3
0

Add extra chart configuration options

This commit is contained in:
Jože Fortun 2023-04-12 11:24:44 +02:00
parent 12f427fa7f
commit 16d0420a9d
11 changed files with 326 additions and 48 deletions

View File

@ -93,6 +93,38 @@
</b-form-checkbox>
</b-form-group>
</b-col>
<b-col
cols="12"
md="6"
>
<b-form-group
:label="$t('edit.metric.angle.start')"
label-class="text-primary"
>
<b-form-input
v-model="metric.startAngle"
type="number"
number
/>
</b-form-group>
</b-col>
<b-col
cols="12"
md="6"
>
<b-form-group
:label="$t('edit.metric.angle.end')"
label-class="text-primary"
>
<b-form-input
v-model="metric.endAngle"
type="number"
number
/>
</b-form-group>
</b-col>
</b-row>
</template>
</report-edit>

View File

@ -129,6 +129,11 @@
>
{{ $t('edit.yAxis.axisScaleFromZero') }}
</b-form-checkbox>
<b-form-checkbox
v-model="report.yAxis.horizontal"
>
{{ $t('edit.yAxis.horizontal.label') }}
</b-form-checkbox>
</b-form-group>
</b-col>
</b-row>
@ -209,6 +214,12 @@
:label="$t('edit.metric.options.label')"
label-class="text-primary"
>
<b-form-checkbox
v-model="metric.fixTooltips"
>
{{ $t('edit.metric.fixTooltips') }}
</b-form-checkbox>
<b-form-checkbox
v-if="hasRelativeDisplay(metric)"
v-model="metric.relativeValue"
@ -216,18 +227,47 @@
{{ $t('edit.metric.relative') }}
</b-form-checkbox>
<b-form-checkbox
v-if="metric.type === 'pie'"
v-model="metric.rose"
>
{{ $t('edit.metric.rose') }}
</b-form-checkbox>
<b-form-checkbox
v-if="metric.type === 'line'"
v-model="metric.fill"
>
{{ $t('edit.metric.fillArea') }}
</b-form-checkbox>
</b-form-group>
<b-form-checkbox
v-model="metric.fixTooltips"
>
{{ $t('edit.metric.fixTooltips') }}
</b-form-checkbox>
<b-form-group
v-if="metric.type === 'line'"
:label="$t('edit.metric.lineStyle.label')"
label-class="text-primary"
>
<b-form-radio-group
:checked="getLineStyle(metric)"
:options="lineStyleOptions"
@change="setLineStyle($event, metric)"
/>
</b-form-group>
</b-col>
<b-col
v-if="!hasRelativeDisplay(metric)"
cols="12"
md="6"
>
<b-form-group
:label="$t('edit.metric.stack.label')"
:description="$t('edit.metric.stack.description')"
label-class="text-primary"
>
<b-form-input
v-model="metric.stack"
/>
</b-form-group>
</b-col>
</b-row>
@ -591,6 +631,12 @@ export default {
{ value: 'center', text: this.$t('edit.additionalConfig.legend.align.center') },
{ value: 'right', text: this.$t('edit.additionalConfig.legend.align.right') },
],
lineStyleOptions: [
{ value: '', text: this.$t('edit.metric.lineStyle.default') },
{ value: 'smooth', text: this.$t('edit.metric.lineStyle.smooth') },
{ value: 'step', text: this.$t('edit.metric.lineStyle.step') },
],
}
},
@ -602,6 +648,17 @@ export default {
methods: {
hasRelativeDisplay: compose.chartUtil.hasRelativeDisplay,
getLineStyle (metric) {
if (metric.smooth) return 'smooth'
else if (metric.step) return 'step'
return ''
},
setLineStyle (style, metric) {
this.$set(metric, 'smooth', style === 'smooth')
this.$set(metric, 'step', style === 'step')
},
},
}
</script>

View File

@ -11,7 +11,7 @@ import { Icon } from 'leaflet'
import ECharts from 'vue-echarts'
import { use } from 'echarts/core'
import {
SVGRenderer,
CanvasRenderer,
} from 'echarts/renderers'
import {
LineChart,
@ -28,6 +28,7 @@ import {
TooltipComponent,
VisualMapComponent,
ToolboxComponent,
DataZoomComponent,
} from 'echarts/components'
use([
@ -37,13 +38,14 @@ use([
GaugeChart,
HeatmapChart,
FunnelChart,
SVGRenderer,
CanvasRenderer,
TitleComponent,
GridComponent,
TooltipComponent,
LegendComponent,
VisualMapComponent,
ToolboxComponent,
DataZoomComponent,
])
Vue.component('e-charts', ECharts)

View File

@ -209,9 +209,56 @@
:report.sync="editReport"
:chart="chart"
:modules="modules"
:dimension-field-kind="['Select']"
:supported-metrics="1"
/>
<hr>
<div
class="px-3"
>
<h5 class="mb-3">
{{ $t('edit.toolbox.label') }}
</h5>
<b-row>
<b-col
cols="12"
md="6"
>
<b-form-group
:label="$t('edit.toolbox.saveAsImage.label')"
label-class="text-primary"
>
<c-input-checkbox
:value="!!chart.config.toolbox.saveAsImage"
switch
:labels="checkboxLabel"
@input="$set(chart.config.toolbox, 'saveAsImage', $event)"
/>
</b-form-group>
</b-col>
<b-col
v-if="hasAxis"
cols="12"
md="6"
>
<b-form-group
:label="$t('edit.toolbox.timeline.label')"
label-class="text-primary"
>
<b-form-radio-group
v-model="chart.config.toolbox.timeline"
buttons
button-variant="outline-secondary"
size="sm"
:options="timelineOptions"
/>
</b-form-group>
</b-col>
</b-row>
</div>
</b-col>
<b-col
@ -227,7 +274,7 @@
:disabled="processing || !reportsValid"
variant="outline-light"
size="lg"
class="d-flex align-items-center text-primary ml-auto border-0 mt-2 mr-2"
class="d-flex align-items-center text-primary ml-auto border-0 px-2 mt-2 mr-2"
@click.prevent="update"
>
<font-awesome-icon :icon="['fa', 'sync']" />
@ -450,6 +497,19 @@ export default {
isEdit () {
return this.chart && this.chart.chartID !== NoID
},
hasAxis () {
return this.reports.some(({ metrics = [] }) => metrics.some(m => ['bar', 'line'].includes(m.type)))
},
timelineOptions () {
return [
{ value: '', text: this.$t('edit.toolbox.timeline.options.none') },
{ value: 'x', text: this.$t('edit.toolbox.timeline.options.x') },
{ value: 'y', text: this.$t('edit.toolbox.timeline.options.y') },
{ value: 'xy', text: this.$t('edit.toolbox.timeline.options.xy') },
]
},
},
watch: {

View File

@ -115,6 +115,11 @@ export class BaseChart {
}
this.config = (conf ? _.merge(this.defConfig(), conf) : false) || this.config || this.defConfig()
this.config.reports?.forEach(report => {
const { dimensions = [], metrics = [] } = report || {}
report.dimensions = dimensions.map(d => _.merge(this.defDimension(), d))
report.metrics = metrics.map(m => _.merge(this.defMetrics(), m))
})
}
/**
@ -361,6 +366,10 @@ export class BaseChart {
colorScheme: '',
reports: [this.defReport()],
noAnimation: false,
toolbox: {
saveAsImage: false,
timeline: '',
},
})
}

View File

@ -25,7 +25,11 @@ export default class Chart extends BaseChart {
type: m.type,
label: m.label || m.field,
data,
fill: !!m.fill,
fill: m.fill,
smooth: m.smooth,
step: m.step ? 'middle' : undefined,
roseType: m.rose ? 'radius' : undefined,
stack: m.stack,
tooltip: {
fixed: m.fixTooltips,
relative: m.relativeValue && !['bar', 'line'].includes(m.type as string),
@ -34,7 +38,8 @@ export default class Chart extends BaseChart {
}
makeOptions (data: any): any {
const { reports = [], colorScheme, noAnimation = false } = this.config
const { reports = [], colorScheme, noAnimation = false, toolbox } = this.config
const { saveAsImage, timeline = '' } = toolbox || {}
const options: any = {
animation: !noAnimation,
@ -70,20 +75,31 @@ export default class Chart extends BaseChart {
beginAtZero,
min,
max,
horizontal,
} = yAxis
const xAxis = {
nameLocation: 'center',
type: 'category',
data: labels,
axisLabel: {
interval: 0,
overflow: 'break',
hideOverlap: true,
rotate: dimension.rotateLabel,
},
}
const tempYAxis = {
name: yLabel,
type: yType === 'linear' ? 'value' : 'log',
position,
nameGap: labelPosition === 'center' ? 30 : 7,
nameLocation: labelPosition,
min: beginAtZero ? 0 : min || undefined,
max: max || undefined,
axisLabel: {
interval: 0,
overflow: 'truncate',
overflow: 'break',
hideOverlap: true,
rotate: yAxis.rotateLabel,
},
@ -93,7 +109,6 @@ export default class Chart extends BaseChart {
},
nameTextStyle: {
align: labelPosition === 'center' ? 'center' : position,
padding: labelPosition !== 'center' ? (position === 'left' ? [0, 0, 2, -3] : [0, -3, 2, 0]) : undefined,
},
}
@ -103,11 +118,17 @@ export default class Chart extends BaseChart {
delete tempYAxis.max
}
options.yAxis = [tempYAxis]
if (horizontal) {
options.xAxis = [tempYAxis]
options.yAxis = [xAxis]
} else {
options.xAxis = [xAxis]
options.yAxis = [tempYAxis]
}
}
}
options.series = datasets.map(({ type, label, data, fill, tooltip }: any, index: number) => {
options.series = datasets.map(({ type, label, data, stack, tooltip, fill, smooth, step, roseType }: any, index: number) => {
const { fixed, relative } = tooltip
const tooltipFormatter = t?.formatting ? t.formatting : `{a}<br />{b} : {c}${relative ? ' ({d}%)' : ''}`
@ -118,10 +139,15 @@ export default class Chart extends BaseChart {
if (['pie', 'doughnut'].includes(type)) {
const startRadius = type === 'doughnut' ? 40 : 0
const endRadius = 80
const radiusLength = (endRadius - startRadius) / (datasets.length || 1)
const sr = startRadius + (index * radiusLength)
const er = startRadius + ((index + 1) * radiusLength)
options.tooltip.trigger = 'item'
let lbl:any = {
let lbl :any = {
rotate: dimension.rotateLabel ? +dimension.rotateLabel: 0
}
@ -143,9 +169,11 @@ export default class Chart extends BaseChart {
return {
z,
stack,
name: label,
type: 'pie',
radius: [`${startRadius}%`, '80%'],
roseType,
radius: [`${sr}%`, `${er}%`],
center: ['50%', '55%'],
tooltip: {
trigger: 'item',
@ -178,33 +206,28 @@ export default class Chart extends BaseChart {
} else if (['bar', 'line'].includes(type)) {
options.tooltip.trigger = 'axis'
if (!options.xAxis.length) {
options.xAxis.push({
nameLocation: 'center',
type: 'category',
data: labels,
axisLabel: {
interval: 0,
overflow: 'truncate',
hideOverlap: true,
rotate: dimension.rotateLabel,
},
})
const defaultOffset = {
top: 50,
right: timeline.includes('x') ? 40 : 30,
bottom: timeline.includes('x') ? 60 : 20,
left: 30,
}
options.grid = {
top: offset?.isDefault ? 50 : offset?.top,
right: offset?.isDefault ? 30 : offset?.right,
bottom: offset?.isDefault ? 20 : offset?.bottom,
left: offset?.isDefault ? 30 : offset?.left,
top: offset?.isDefault ? defaultOffset.top : offset?.top,
right: offset?.isDefault ? defaultOffset.right : offset?.right,
bottom: offset?.isDefault ? defaultOffset.bottom : offset?.bottom,
left: offset?.isDefault ? defaultOffset.left : offset?.left,
containLabel: true,
}
return {
z,
stack,
name: label,
type: type,
smooth: true,
smooth,
step,
areaStyle: {
opacity: fill ? 0.7 : 0,
},
@ -220,11 +243,37 @@ export default class Chart extends BaseChart {
}
})
const dataZoom = timeline ? [
{
show: timeline.includes('x'),
type: 'slider',
height: 30,
},
{
show: timeline.includes('y'),
type: 'slider',
width: 15,
yAxisIndex: 0,
},
] : undefined
return {
color: getColorschemeColors(colorScheme),
textStyle: {
fontFamily: 'Poppins-Regular',
overflow: 'break',
},
toolbox: {
feature: {
saveAsImage: saveAsImage ? {
name: this.name
} : undefined,
},
top: 15,
right: 5,
},
dataZoom,
legend: {
show: !l?.isHidden,
type: l?.isScrollable ? 'scroll' : 'plain',
@ -238,6 +287,14 @@ export default class Chart extends BaseChart {
}
}
defMetrics (): Metric {
return Object.assign({}, {
smooth: true,
fill: false,
rose: false,
})
}
baseChartType (datasets: Array<any>): string {
return datasets[0].type
}

View File

@ -85,7 +85,9 @@ export default class FunnelChart extends BaseChart {
}
makeOptions (data: any) {
const { colorScheme, noAnimation = false } = this.config
const { colorScheme, noAnimation = false, toolbox } = this.config
const { saveAsImage } = toolbox || {}
const { labels, datasets = [], tooltip } = data
const colors = getColorschemeColors(colorScheme)
@ -97,6 +99,15 @@ export default class FunnelChart extends BaseChart {
textStyle: {
fontFamily: 'Poppins-Regular',
},
toolbox: {
feature: {
saveAsImage: saveAsImage ? {
name: this.name
} : undefined,
},
top: 15,
right: 5,
},
tooltip: {
show: true,
trigger: 'item',
@ -111,7 +122,7 @@ export default class FunnelChart extends BaseChart {
return {
type: 'funnel',
sort: 'descending',
top: 35,
top: 45,
bottom: 10,
left: '5%',
width: '90%',

View File

@ -65,6 +65,8 @@ export default class GaugeChart extends BaseChart {
name,
max,
value,
startAngle: m.startAngle,
endAngle: m.endAngle,
tooltip: {
fixed: m.fixTooltips,
},
@ -72,9 +74,11 @@ export default class GaugeChart extends BaseChart {
}
makeOptions (data: any) {
const { colorScheme, noAnimation = false } = this.config
const { colorScheme, noAnimation = false, toolbox } = this.config
const { saveAsImage } = toolbox || {}
const { datasets = [] } = data
const { steps = [], name, value, max, tooltip } = datasets.find(({ value }: any) => value) || datasets[0]
const { steps = [], name, value, max, tooltip, startAngle, endAngle } = datasets.find(({ value }: any) => value) || datasets[0]
const colors = getColorschemeColors(colorScheme)
const color = steps.map((s: any, i: number) => {
@ -86,19 +90,28 @@ export default class GaugeChart extends BaseChart {
textStyle: {
fontFamily: 'Poppins-Regular',
},
toolbox: {
feature: {
saveAsImage: saveAsImage ? {
name: this.name
} : undefined,
},
top: 15,
right: 5,
},
grid: {
bottom: 0,
},
series: [
{
type: 'gauge',
startAngle: 200,
endAngle: -20,
startAngle,
endAngle,
min: 0,
max,
splitNumber: 5,
radius: '100%',
center: ['50%', '60%'],
center: ['50%', '50%'],
pointer: {
width: 5,
length: '75%',
@ -153,7 +166,11 @@ export default class GaugeChart extends BaseChart {
}
defMetrics (): Metric {
return Object.assign({}, { type: ChartType.gauge })
return Object.assign({}, {
type: ChartType.gauge,
startAngle: 200,
endAngle: -20
})
}
/**

View File

@ -57,6 +57,7 @@ export interface YAxis {
min?: string;
max?: string;
rotateLabel?: number;
horizontal?: boolean;
}
export interface ChartOffset {
@ -100,10 +101,16 @@ export interface Report {
offset?: ChartOffset;
}
interface ChartToolbox {
saveAsImage: boolean;
timeline: string;
}
export interface ChartConfig {
reports?: Array<Report>;
colorScheme?: string;
noAnimation?: boolean;
toolbox?: ChartToolbox
}
export const aggregateFunctions = [

View File

@ -79,6 +79,18 @@ edit:
fx:
description: n - current dataset value, m - previous dataset value
label: Post processing function
stack:
label: Stack
description: Metrics with same value will be stacked
lineStyle:
label: Line style
default: Default
smooth: Smooth
step: Step
angle:
start: Start angle
end: End angle
rose: Rose
gaugeColor: Color
label: Metric
labelColor: Label color
@ -126,9 +138,11 @@ edit:
labelLabel: Axis label
labelPosition:
label: Label position
top: Top
top: Start
center: Center
bottom: Bottom
bottom: End
horizontal:
label: Horizontal
logarithmicScale: Logarithmic scale
maxLabel: Max value
maxPlaceholder: Maximum value
@ -173,6 +187,17 @@ edit:
bottom: Bottom
left: Left
valueRange: Values can be set as pixels 20 or as percentages 20%
toolbox:
label: Tools
saveAsImage:
label: Show "Save as Image" button
timeline:
label: Timeline
options:
none: None
x: X-Axis
y: Y-Axis
xy: Both
import: 'Import chart(s):'
newLabel: 'Create a new chart:'
name: Chart name *

View File

@ -31,9 +31,10 @@ type (
}
ChartConfig struct {
Reports []*ChartConfigReport `json:"reports,omitempty"`
ColorScheme string `json:"colorScheme,omitempty"`
NoAnimation bool `json:"noAnimation,omitempty"`
Reports []*ChartConfigReport `json:"reports,omitempty"`
ColorScheme string `json:"colorScheme,omitempty"`
NoAnimation bool `json:"noAnimation,omitempty"`
Toolbox map[string]interface{} `json:"toolbox,omitempty"`
}
ChartConfigReport struct {