Commit 515865ca authored by Nicolas Frandeboeuf's avatar Nicolas Frandeboeuf
Browse files

Use ApexCharts instead of C3

parent 737787ad
Pipeline #79312 failed with stages
in 1 minute and 29 seconds
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Select from 'react-select';
import { translate as $t } from '../../helpers';
import { get } from '../../store';
const CategorySelect = props => {
const handleChange = values => {
if (values.some(v => v.value === "selectall")) {
values = props.options;
} else {
values = values.filter(v => v !== "selectall");
}
props.onChange(values.map(v => v.value));
};
const values = props.options.filter(
opt => props.values.includes(opt.value)
);
const options = [
{
value: 'selectall',
label: 'Select all'
},
...props.options
];
// Never display "Select all" as selected.
const isOptionSelected = (opt, selected) => (
opt.value !== "selectall" &&
selected.some(s => s.value === opt.value)
);
// Disable "Select all" if everything is already selected.
const isOptionDisabled = (opt, selected) => (
opt.value === "selectall" &&
selected.length >= props.options.length
);
return (
<Select
isMulti={true}
isSearchable={false}
isClearable={true}
defaultValue={values}
onChange={handleChange}
options={options}
placeholder={ $t('client.charts.select_categories') }
closeMenuOnSelect={false}
backspaceRemovesValue={false}
hideSelectedOptions={false}
classNamePrefix="Select"
controlShouldRenderValue={false}
isOptionSelected={isOptionSelected}
isOptionDisabled={isOptionDisabled}
/>
);
}
CategorySelect.propTypes = {
// The ids of the categories selected.
values: PropTypes.array.isRequired,
// A callback called whenever one of the inputs change.
onChange: PropTypes.func.isRequired
};
const Export = connect(state => {
const options = get.categories(state).map(c => (
{ value: c.id, label: c.title }
));
return {
options
};
})(CategorySelect);
export default Export;
\ No newline at end of file
......@@ -6,10 +6,10 @@ export default class ChartComponent extends React.Component {
}
componentDidUpdate() {
this.redraw();
//this.redraw();
}
componentDidMount() {
this.redraw();
//this.redraw();
}
}
import React from 'react';
import c3 from 'c3';
import ApexCharts from 'apexcharts'
import { translate as $t, round2, getWellsColors } from '../../helpers';
......@@ -81,7 +81,25 @@ function createChartPositiveNegative(chartId, operations, theme) {
let highExtent = dates.length;
let yAxisLegend = $t('client.charts.amount');
var options = {
chart: {
type: 'bar',
height: CHART_SIZE
},
series: [{
name: yAxisLegend,
data: series
//data: [30,40,35,50,49,60,70,91,125]
}],
xaxis: {
type: 'datetime'
}
};
/*
c3.generate({
bindto: chartId,
......@@ -137,11 +155,14 @@ function createChartPositiveNegative(chartId, operations, theme) {
rescale: true
}
});
*/
var chart = new ApexCharts(document.getElementById(chartId), options);
chart.render();
}
export default class InOutChart extends ChartComponent {
redraw() {
createChartPositiveNegative('#barchart', this.props.operations, this.props.theme);
createChartPositiveNegative('barchart', this.props.operations, this.props.theme);
}
render() {
......
import React from 'react';
import { connect } from 'react-redux';
import c3 from 'c3';
import ApexCharts from 'react-apexcharts';
import { assert, round2, translate as $t } from '../../helpers';
import { get } from '../../store';
import OpCatChartPeriodSelect from './operations-by-category-period-select';
import OpAmountTypeSelect from './operations-by-amount-type-select';
import CategorySelect from './category-select';
import ChartComponent from './chart-base';
// Charts algorithms.
function createBarChartAll(getCategoryById, operations, invertSign, barchartId) {
function generateBarChartAllData(getCategoryById, operations, invertSign) {
function datekey(op) {
let d = op.budgetDate;
return `${d.getFullYear()}-${d.getMonth()}`;
......@@ -20,8 +20,7 @@ function createBarChartAll(getCategoryById, operations, invertSign, barchartId)
// Category -> {Month -> [Amounts]}
let map = new Map();
// Category -> color
let colorMap = {};
let colors = [];
// Datekey -> Date
let dateset = new Map();
......@@ -37,7 +36,7 @@ function createBarChartAll(getCategoryById, operations, invertSign, barchartId)
(categoryDates[dk] = categoryDates[dk] || []).push(amount);
dateset.set(dk, +op.budgetDate);
colorMap[c.title] = colorMap[c.title] || c.color;
colors.push(c.color);
}
// Sort date in ascending order: push all pairs of (datekey, date) in an
......@@ -48,16 +47,16 @@ function createBarChartAll(getCategoryById, operations, invertSign, barchartId)
let series = [];
for (let c of map.keys()) {
let data = [];
for (let j = 0; j < dates.length; j++) {
let dk = dates[j][0];
let values = (map.get(c)[dk] = map.get(c)[dk] || []);
data.push(round2(values.reduce((a, b) => a + b, 0)));
}
data = [c].concat(data);
series.push(data);
let data = dates.map(d => {
let dk = d[0];
let values = map.get(c)[dk] || [];
return round2(values.reduce((a, b) => a + b, 0));
});
series.push({
name: c,
data
});
}
let categories = [];
......@@ -71,51 +70,15 @@ function createBarChartAll(getCategoryById, operations, invertSign, barchartId)
});
categories.push(str);
}
let yAxisLegend = $t('client.charts.amount');
return c3.generate({
bindto: barchartId,
data: {
columns: series,
type: 'bar',
colors: colorMap
},
bar: {
width: {
ratio: 0.5
}
},
axis: {
x: {
type: 'category',
categories,
tick: {
fit: false
}
},
y: {
label: yAxisLegend
}
},
grid: {
x: {
show: true
},
y: {
show: true,
lines: [{ value: 0 }]
}
}
});
return {
series,
categories,
colors
};
}
function createPieChartAll(getCategoryById, operations, chartId) {
function generatePieChartAllData(getCategoryById, operations, invertSign) {
let catMap = new Map();
// categoryId -> [val1, val2, val3]
for (let op of operations) {
......@@ -127,31 +90,20 @@ function createPieChartAll(getCategoryById, operations, chartId) {
// [ [categoryName, val1, val2], [anotherCategoryName, val3, val4] ]
let series = [];
// {label -> color}
let colorMap = {};
let colors = [];
let categories = [];
for (let [catId, valueArr] of catMap) {
let c = getCategoryById(catId);
series.push([c.title].concat(valueArr));
colorMap[c.title] = c.color;
colors.push(c.color);
series.push(round2(valueArr.reduce((a, b) => a + b, 0)));
categories.push(c.title);
}
return c3.generate({
bindto: chartId,
data: {
columns: series,
type: 'pie',
colors: colorMap
},
tooltip: {
format: {
value(value, ratio) {
return `${round2(ratio * 100)}% (${Math.abs(round2(value))})`;
}
}
}
});
return {
series,
colors,
categories
};
}
class OpCatChart extends ChartComponent {
......@@ -161,23 +113,25 @@ class OpCatChart extends ChartComponent {
this.state = {
showPositiveOps: props.showPositiveOps,
showNegativeOps: props.showNegativeOps,
period: props.defaultPeriod
period: props.defaultPeriod,
categories: props.categories
};
this.handleRedraw = this.redraw.bind(this);
this.handleHideAll = this.handleHideAll.bind(this);
this.handleShowAll = this.handleShowAll.bind(this);
this.handleAmountTypeChange = this.setState.bind(this);
this.handleChangePeriod = this.handleChangePeriod.bind(this);
this.handleChangeCategories = this.handleChangeCategories.bind(this);
}
handleChangePeriod(event) {
this.setState(
{
period: event.target.value
},
this.handleRedraw
);
this.setState({
period: event.target.value
});
}
handleChangeCategories(values) {
this.setState({
categories: values
});
}
createPeriodFilter(option) {
......@@ -224,53 +178,87 @@ class OpCatChart extends ChartComponent {
}
}
redraw() {
let ops = this.props.operations.slice();
// Period
let period = this.state.period;
let periodFilter = this.createPeriodFilter(period);
ops = ops.filter(op => periodFilter(op.budgetDate));
// Kind
if (this.state.showNegativeOps && !this.state.showPositiveOps) {
ops = ops.filter(op => op.amount < 0);
} else if (!this.state.showNegativeOps && this.state.showPositiveOps) {
ops = ops.filter(op => op.amount > 0);
}
// Invert values if and only if we only display the negative chart.
let invertSign = this.state.showNegativeOps && !this.state.showPositiveOps;
// Print charts
this.barchart = createBarChartAll(this.props.getCategoryById, ops, invertSign, '#barchart');
if (!this.state.showPositiveOps || !this.state.showNegativeOps) {
this.piechart = createPieChartAll(this.props.getCategoryById, ops, '#piechart');
} else {
document.querySelector('#piechart').innerHTML = '';
this.piechart = null;
}
}
handleShowAll() {
if (this.barchart) {
this.barchart.show();
}
if (this.piechart) {
this.piechart.show();
}
}
handleHideAll() {
if (this.barchart) {
this.barchart.hide();
}
if (this.piechart) {
this.piechart.hide();
}
}
render() {
let barChart = null;
let pieChart = null;
if (this.state.categories.length > 0) {
let ops = this.props.operations.slice();
// Period
let period = this.state.period;
let periodFilter = this.createPeriodFilter(period);
ops = ops.filter(op => periodFilter(op.budgetDate));
// Categories
ops = ops.filter(op => this.state.categories.includes(op.categoryId));
// Kind
if (this.state.showNegativeOps && !this.state.showPositiveOps) {
ops = ops.filter(op => op.amount < 0);
} else if (!this.state.showNegativeOps && this.state.showPositiveOps) {
ops = ops.filter(op => op.amount > 0);
}
// Invert values if and only if we only display the negative chart.
let invertSign = this.state.showNegativeOps && !this.state.showPositiveOps;
let data = generateBarChartAllData(this.props.getCategoryById, ops, invertSign);
let options = {
legend: {
onItemClick: {
toggleDataSeries: false
}
},
colors: data.colors,
xaxis: {
categories: data.categories
},
yaxis: {
title: {
text: $t('client.charts.amount')
}
},
tooltip: {
shared: true
}
};
barChart = (
<ApexCharts
options={options}
series={data.series}
type="bar"
/>
);
if (!this.state.showPositiveOps || !this.state.showNegativeOps) {
data = generatePieChartAllData(this.props.getCategoryById, ops);
options = {
legend: {
onItemClick: {
toggleDataSeries: false
}
},
colors: data.colors,
labels: data.categories
};
pieChart = (
<ApexCharts
options={options}
series={data.series}
type="pie"
/>
);
}
}
return (
<React.Fragment>
<form>
......@@ -298,20 +286,16 @@ class OpCatChart extends ChartComponent {
<div>
<label>{$t('client.menu.categories')}</label>
<p className="buttons-group" role="group" aria-label="Show/Hide categories">
<button type="button" className="kbtn" onClick={this.handleHideAll}>
{$t('client.general.unselect_all')}
</button>
<button type="button" className="kbtn" onClick={this.handleShowAll}>
{$t('client.general.select_all')}
</button>
</p>
<CategorySelect
values={this.state.categories}
onChange={this.handleChangeCategories}
/>
</div>
</form>
<div id="barchart" style={{ width: '100%' }} />
{ barChart }
<div id="piechart" style={{ width: '100%' }} />
{ pieChart }
</React.Fragment>
);
}
......@@ -327,7 +311,8 @@ const Export = connect(state => {
showPositiveOps,
showNegativeOps,
defaultPeriod,
getCategoryById: id => get.categoryById(state, id)
getCategoryById: id => get.categoryById(state, id),
categories: get.categories(state).map(c => c.id)
};
})(OpCatChart);
......
......@@ -107,7 +107,8 @@
"category_chart": "Category chart",
"default_display": "Default display type",
"default_amount_type": "Default amount type",
"default_period": "Default period"
"default_period": "Default period",
"select_categories": "Click to select the categories"
},
"general": {
"cancel": "Cancel",
......
......@@ -107,7 +107,8 @@
"category_chart": "Graphique par catégories",
"default_display": "Type d'affichage par défaut",
"default_amount_type": "Type d'opérations par défaut",
"default_period": "Période par défaut"
"default_period": "Période par défaut",
"select_categories": "Cliquez pour sélectionner les catégories"
},
"general": {
"cancel": "Annuler",
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment