Hi,
I am creating a component where I populate the dashboard layout panels dynamically using a javascript object.
This is how it looks like.
When I click new block I get a pop up to select the chart type as below
By selecting the chart type I update the following json (which i use to populate the charts)
[
{
type: "spline",
title: "Chart Title",
data: {
id: "onespline",
position: {
row: "0",
col: "0"
},
seriesData: [ { x: 'Jan', y: 46 }, { x: 'Feb', y: 27 }, { x: 'Mar', y: 26 } ],
seriesData1: [ { x: 'Jan', y: 37 }, { x: 'Feb', y: 23 }, { x: 'Mar', y: 18 } ],
seriesData2: [ { x: 'Jan', y: 38 }, { x: 'Feb', y: 17 }, { x: 'Mar', y: 26 } ],
primaryXAxis: {
valueType: 'Category',
interval: 1,
majorGridLines: { width: 0 }
}
}
}
]
Json gets updated properly. But the newly added chart is un draggable and size is wrong. Following is the screen shot of the result.
Followings are my project files.
Package.json
{
"name": "dynamic_dash",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"@syncfusion/ej2-base": "^19.3.47",
"@syncfusion/ej2-charts": "^19.3.46",
"@syncfusion/ej2-compression": "^19.3.43",
"@syncfusion/ej2-data": "^19.3.47",
"@syncfusion/ej2-file-utils": "^19.3.43",
"@syncfusion/ej2-layouts": "^19.3.44",
"@syncfusion/ej2-pdf-export": "^19.3.43",
"@syncfusion/ej2-svg-base": "^19.3.45",
"@syncfusion/ej2-vue-base": "^19.3.47",
"@syncfusion/ej2-vue-buttons": "^19.3.44",
"@syncfusion/ej2-vue-charts": "^19.3.46",
"@syncfusion/ej2-vue-dropdowns": "^19.3.47",
"@syncfusion/ej2-vue-layouts": "^19.3.44",
"@syncfusion/ej2-vue-navigations": "^19.3.46",
"@syncfusion/ej2-vue-popups": "^19.3.47",
"bootstrap-vue": "^2.21.2",
"core-js": "^3.6.5",
"vue": "^2.6.11"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"node-sass": "^4.12.0",
"sass-loader": "^8.0.2",
"vue-template-compiler": "^2.6.11"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
Main.js
import Vue from 'vue'
import App from './App.vue'
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
Vue.use(BootstrapVue)
Vue.use(IconsPlugin)
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
App.vue
<template>
<div id="app">
<DashboardLayout :charts="charts" v-on:addBlockModal="toggleBlockModal(true)"/>
<add-block-modal :show="showAddBlockModal" v-on:hideTab="toggleBlockModal(false)" v-on:addBlock="pushNewBlock($event)" />
</div>
</template>
<script>
import AddBlockModal from './components/shared/AddBlockModal.vue';
import DashboardLayout from "./components/DashboardLayout.vue";
export default {
name: 'MainDashboardComponent',
components: {
AddBlockModal,
DashboardLayout
},
data: function(){
return {
showAddBlockModal: false,
charts: undefined
}
},
created: function() {
let updatedCharts = [
{
type: "spline",
title: "Chart Title",
data: {
id: "onespline",
position: {
row: "0",
col: "0"
},
seriesData: [ { x: 'Jan', y: 46 }, { x: 'Feb', y: 27 }, { x: 'Mar', y: 26 } ],
seriesData1: [ { x: 'Jan', y: 37 }, { x: 'Feb', y: 23 }, { x: 'Mar', y: 18 } ],
seriesData2: [ { x: 'Jan', y: 38 }, { x: 'Feb', y: 17 }, { x: 'Mar', y: 26 } ],
primaryXAxis: {
valueType: 'Category',
interval: 1,
majorGridLines: { width: 0 }
}
}
}
]
this.charts = updatedCharts;
},
methods: {
toggleBlockModal: function(state) {
this.showAddBlockModal = state;
},
pushNewBlock: function(chartType) {
var chartId = Math.ceil(Math.random()*10000000) + chartType; // Generate id for new chart
var numberOfCharts = this.charts.length - 1; // lenght of sections - add new block
// Calculate the position data (row and col)
var rowNum = Math.floor(numberOfCharts/2) * 3;
var colNum = (numberOfCharts%2)*3;
var newChart;
if(chartType == 'pie') {
newChart = {
type: "pie",
title: "",
data: {
id: chartId,
position: {
row: rowNum,
col: colNum
},
seriesData: [
{ x: "TypeScript", y: 13, text: "TS 13%" },
{ x: "React", y: 12.5, text: "Reat 12.5%" },
{ x: "MVC", y: 12, text: "MVC 12%" },
{ x: "Core", y: 12.5, text: "Core 12.5%" },
{ x: "Vue", y: 10, text: "Vue 10%" },
{ x: "Angular", y: 40, text: "Angular 40%" }
],
legendSettings: { visible: false },
dataLabel: { visible: true, position: "Inside", name: "value" },
tooltip: {
enable: true
}
}
}
} else if (chartType == 'bar') {
newChart = {
type: "spline",
title: "By Job Level",
data: {
id: chartId,
position: {
row: rowNum,
col: colNum
},
seriesData: [ { x: 'Jan', y: 46 }, { x: 'Feb', y: 27 }, { x: 'Mar', y: 26 } ],
seriesData1: [ { x: 'Jan', y: 37 }, { x: 'Feb', y: 23 }, { x: 'Mar', y: 18 } ],
seriesData2: [ { x: 'Jan', y: 38 }, { x: 'Feb', y: 17 }, { x: 'Mar', y: 26 } ],
primaryXAxis: {
valueType: 'Category',
interval: 1,
majorGridLines: { width: 0 }
}
}
}
}
this.charts.splice(this.charts.length-1, 0, newChart);
}
}
}
</script>
<style lang="scss">
@import "../node_modules/@syncfusion/ej2-base/styles/material.css";
@import "../node_modules/@syncfusion/ej2-vue-buttons/styles/material.css";
@import "../node_modules/@syncfusion/ej2-vue-popups/styles/material.css";
@import "../node_modules/@syncfusion/ej2-vue-navigations/styles/material.css";
.d-flex {
display: flex;
&.wrap {
flex-wrap: wrap;
}
&.justify {
&-start{
justify-content: flex-start;
}
&-around {
justify-content: space-around;
}
&-between {
justify-content: space-between;
}
}
&.align-center {
align-items: center;
}
.dropdown {
width: 15%;
margin-right: 10px;
}
}
.kebab-menu {
float: right;
}
</style>
components/DashboardLayout.vue
<template>
<div>
<ejs-dashboardlayout id='dashboardlayoutid' :cellSpacing="cellSpacing ? cellSpacing : [20, 20]" :columns="6" v-if="charts[0].data">
<div :style="{ display: chart.type=='newBlock' ? 'none' : '' }" v-for="(chart, chartIndex) in charts" :key="chartIndex" :id="'one'+chartIndex" class="e-panel" :data-row="(chart.data && chart.data.position) ? chart.data.position.row : 0" :data-col="(chart.data && chart.data.position) ? chart.data.position.col : 0" :data-sizeX="3" :data-sizeY="3">
<div class="e-panel-container">
<div class="text-align" v-if="chart.type=='spline'">
<div>
<Spline />
</div>
</div>
<div class="text-align" v-if="chart.type=='pie'">
<div>
<Pie />
</div>
</div>
</div>
</div>
</ejs-dashboardlayout>
<div style="margin-top: 10px;">
<div class="add-new-block d-flex justify-content-center align-items-center flex-column" v-on:click="addBlockModal()">
<span class="add-icon-blue"></span>
<p class="add-block-text">ADD BLOCK</p>
</div>
</div>
</div>
</template>
<script>
import Vue from "vue";
import { DashboardLayoutPlugin } from "@syncfusion/ej2-vue-layouts";
import Spline from "./charts/Spline.vue";
import LineChart from "./charts/LineChart.vue";
import Pie from "./charts/Pie.vue";
Vue.use(DashboardLayoutPlugin);
export default {
components: {
Spline,
LineChart,
Pie
},
props: {
charts: {
type: Array
},
newBlock: {
type: Boolean
},
id: {
type: String
}
},
data: function() {
return {
cellSpacing: [20, 20],
blockSize: 3
};
},
methods: {
addBlockModal() {
this.$emit('addBlockModal');
}
}
}
</script>
<style lang="scss" scoped>
@import "../../node_modules/@syncfusion/ej2-base/styles/material.css";
@import "../../node_modules/@syncfusion/ej2-vue-layouts/styles/material.css";
@import "../../node_modules/@syncfusion/ej2-inputs/styles/material.css";
@import "../../node_modules/@syncfusion/ej2-vue-dropdowns/styles/material.css";
.add-new-block {
color: #3FA8F5;
background: rgba(63, 168, 245, 0.1);
border: 1px dashed;
padding: 150px 120px;
&:hover {
cursor: pointer;
}
.add-icon-blue {
width: 40px;
height: 40px;
background-image: url("../assets/images/add-icon-blue.png");
background-repeat: no-repeat;
}
.add-block-text {
margin-top: 10px;
}
}
.control-section {
.d-flex {
display: flex;
&.justify-start {
justify-content: flex-start;
}
&.align-center {
align-items: center;
}
.dropdown {
width: 15%;
margin-right: 10px;
}
}
}
#dashboard_default .e-panel .e-panel-container .content {
vertical-align: middle;
font-weight: 600;
font-size: 20px;
text-align: center;
line-height: 100px;
}
#dashboard_default .e-panel {
transition:none !important;
}
</style>
components/charts/pie.vue
<template>
<div id="container2" style='display:block;height:100%; width:100%;'>
<ejs-accumulationchart class="chart-content" :legendSettings="legendSettings" :tooltip="tooltip" :width="chartWidth" :height="chartHeight">
<e-accumulation-series-collection>
<e-accumulation-series :dataSource='seriesData' xName='x' yName='y' innerRadius="40%" :dataLabel="dataLabel"> </e-accumulation-series>
</e-accumulation-series-collection>
</ejs-accumulationchart>
</div>
</template>
<script>
import Vue from "vue";
import { AccumulationChartPlugin, PieSeries, AccumulationDataLabel, AccumulationTooltip, ChartPlugin} from "@syncfusion/ej2-vue-charts";
import { ButtonPlugin } from "@syncfusion/ej2-vue-buttons";
Vue.use(ButtonPlugin);
Vue.use(ChartPlugin);
Vue.use(AccumulationChartPlugin);
export default {
name: "Pie",
data() {
return {
seriesData: [
{ x: "TypeScript", y: 13, text: "TS 13%" },
{ x: "React", y: 12.5, text: "Reat 12.5%" },
{ x: "MVC", y: 12, text: "MVC 12%" },
{ x: "Core", y: 12.5, text: "Core 12.5%" },
{ x: "Vue", y: 10, text: "Vue 10%" },
{ x: "Angular", y: 40, text: "Angular 40%" }
],
legendSettings: { visible: false },
dataLabel: { visible: true, position: "Inside", name: "value" },
tooltip: {
enable: true
},
chartWidth: "99%",
chartHeight: "99%"
};
},
provide: {
accumulationchart: [PieSeries, AccumulationDataLabel, AccumulationTooltip]
},
mounted() {
this.chartWidth = "100%";
this.chartHeight = "100%";
}
}
</script>
components/charts/Spline.vue
<template>
<div id="container1" style='display:block;height:100%, width:100%;'>
<!-- Chart element declaration -->
<ejs-chart class="chart-content" :primaryXAxis='primaryXAxis' :width="chartWidth" :height="chartHeight">
<e-series-collection>
<e-series :dataSource='seriesData' type='Column' xName='x' yName='y' name='Jan'> </e-series>
<e-series :dataSource='seriesData1' type='Column' xName='x' yName='y' name='Feb' fill="rgb(239, 183, 202)"> </e-series>
<e-series :dataSource='seriesData2' type='Column' xName='x' yName='y' name='Mar'> </e-series>
</e-series-collection>
</ejs-chart>
<!-- end of chart element -->
</div>
</template>
<script>
import Vue from "vue";
import { AccumulationChartPlugin, ChartPlugin, ColumnSeries, Legend, Category } from "@syncfusion/ej2-vue-charts";
import { ButtonPlugin } from "@syncfusion/ej2-vue-buttons";
Vue.use(ButtonPlugin);
Vue.use(ChartPlugin);
Vue.use(AccumulationChartPlugin);
export default {
name: "Spline",
data: function() {
return {
seriesData: [ { x: 'Jan', y: 46 }, { x: 'Feb', y: 27 }, { x: 'Mar', y: 26 } ],
seriesData1: [ { x: 'Jan', y: 37 }, { x: 'Feb', y: 23 }, { x: 'Mar', y: 18 } ],
seriesData2: [ { x: 'Jan', y: 38 }, { x: 'Feb', y: 17 }, { x: 'Mar', y: 26 } ],
primaryXAxis: {
valueType: 'Category',
interval: 1,
majorGridLines: { width: 0 }
},
chartWidth: "99%",
chartHeight: "99%"
};
},
provide: {
chart: [ColumnSeries, Category, Legend]
},
mounted() {
this.chartWidth = "100%";
this.chartHeight = "100%";
}
}
</script>
<style></style>
components/shared/AddBlockModal.vue
<template>
<div class="addTabModal" :style="{ display: show ? '' : 'none' }">
<div class="addTabModal__body">
<div class="blockSelection d-flex justify-around align-center wrap">
<div class="blockSelection__blockContainer" v-on:click="addBlock('pie'); hideTab();">
<div class="blockSelection__block blockSelection__block--pie">Pie chart</div>
</div>
<div class="blockSelection__blockContainer" v-on:click="addBlock('pie'); hideTab();">
<div class="blockSelection__block blockSelection__block--pie">Pie chart</div>
</div>
<div class="blockSelection__blockContainer" v-on:click="addBlock('pie'); hideTab();">
<div class="blockSelection__block blockSelection__block--pie">Pie chart</div>
</div>
<div class="blockSelection__blockContainer" v-on:click="addBlock('bar'); hideTab();">
<div class="blockSelection__block blockSelection__block--bar">bar chart</div>
</div>
<div class="blockSelection__blockContainer" v-on:click="addBlock('bar'); hideTab();">
<div class="blockSelection__block blockSelection__block--bar">bar chart</div>
</div>
<div class="blockSelection__blockContainer" v-on:click="addBlock('bar'); hideTab();">
<div class="blockSelection__block blockSelection__block--bar">bar chart</div>
</div>
<div class="blockSelection__blockContainer" v-on:click="addBlock('bar'); hideTab();">
<div class="blockSelection__block blockSelection__block--bar">bar chart</div>
</div>
<div class="blockSelection__blockContainer" v-on:click="addBlock('bar'); hideTab();">
<div class="blockSelection__block blockSelection__block--bar">bar chart</div>
</div>
<div class="blockSelection__blockContainer" v-on:click="addBlock('bar'); hideTab();">
<div class="blockSelection__block blockSelection__block--bar">bar chart</div>
</div>
</div>
<div class="addTabModal__buttons">
<button class="addTabModal__button addTabModal__button--cancel" v-on:click="hideTab">CANCEL</button>
<button class="addTabModal__button addTabModal__button--add" v-on:click="addNewSection(); hideTab();">CREATE</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: "AddTabModal",
props: {
show: {
type: Boolean,
default: false
}
},
methods: {
hideTab: function() {
this.$emit('hideTab');
},
addBlock: function(type) {
this.$emit('addBlock', type);
}
}
}
</script>
<style scoped lang="scss">
.addTabModal {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: rgba(0,0,0,0.78);
display: flex;
align-items: center;
justify-content: center;
z-index: 2500;
height: 100%;
&__body {
background: #fff;
padding: 32px;
width: 800px;
}
&__title {
font-weight: 700;
size: 20px;
line-height: 30px;
color: #626262;
}
&__description {
font-weight: 400;
font-size: 15px;
line-height: 24px;
color: #626262;
}
&__label {
font-weight: 600;
font-size: 18px;
line-height: 28px;
color: #707070;
display: block;
}
&__input {
padding: 16px;
width: 100%;
color: #626262;
border: 1px solid;
&:focus {
outline: none;
}
}
&__buttons {
display: flex;
align-items: center;
justify-content: flex-start;
margin-top: 20px;
}
&__button {
width: 160px;
padding: 12px 0px;
color: #fff;
font-weight: 600;
font-size: 17px;
border: none;
margin-right: 15px;
&--cancel {
background: #4f4f4f;
}
&--add {
background: #3FA8F5;
}
}
}
.blockSelection {
width: 100%;
&__blockContainer {
width: 30%;
height: 120px;
padding: 10px;
background: #F8F8F8;
margin-bottom: 10px;
}
&__block {
width: 100%;
height: 100%;
background-repeat: no-repeat;
background-size: cover;
}
}
</style>