Related
This question will be bountied as soon as I can, so a big rep reward coming to someone!
EDIT: One more layer to this issue I noticed, The lowest level children when I try to drag it somewhere else it doesn't work, acts like it will but goes back where it was (i.e it's children are empty). But when I drag the parent of a child somewhere else it does one of the following:
Moves the parent and it's parent somewhere else where I drag it, but leaves the child where it was i.e
Initial state on a refresh
After I drag col-2 to container-1
or
Moves the child into the container where I dragged the parent to but leaves the parent where it was i.e
Initial state on a refresh
After dragging col-2 into col-1
Original Post
Hey guys, been building a landing page builder and decided vue-draggable would be a nice addition. That said after 3 days of headaches trying to make this work I'm at a loss. So far I've followed the nested example guide which has been KINDA working, in addition, I followed an issue about the nested guide on here adding an emitter to the children for proper updates. Now my getters and setters are firing BUT I'm still having a problem dragging elements(see the video)
http://www.giphy.com/gifs/ZMiyi8LEcI73nye1ZN
As you can see when I drag stuff around it's strange behavior:
Example cases:
When I drag col 2 label into col 1 it moves the children inside into
col one, does not change col 2s place
When I drag paragraph label
anywhere it will not move, shows like it will but when I release
nothing happens
If I drag row 1 from the original starting state you
saw in the gif into the paragraph I end up with the following:
Just 3 sample cases, references:
https://sortablejs.github.io/Vue.Draggable/#/nested-with-vmodel
https://github.com/SortableJS/Vue.Draggable/issues/701#issuecomment-686187071
my code creating these results:
component-renderer.vue (THERE'S A NOTE IN HERE TO READ)
<draggable
v-bind="dragOptions"
:list="list"
:value="value"
style="position: relative; border: 1px solid red"
:tag="data.tagName"
:class="data.attributes.class + ` border border-danger p-3`"
#input="emitter"
#change="onChange" //NOTE: I've tried setting the group here to row instead of in the computed prop below, didn't work
>
<slot></slot>
<component-renderer
v-for="el in realValue"
:key="el.attributes.id"
:list="el.children"
:data="el"
:child="true"
#change="onChange"
>
<span style="position: absolute; top: 0; left: 0; background: red">{{
`${el.tagName} - ${el.attributes.id}`
}}</span>
{{ el.textNode }}
</component-renderer>
</draggable>
</template>
<script>
import draggable from "vuedraggable";
export default {
name: "ComponentRenderer",
components: {
draggable,
},
props: {
data: {
required: false,
type: Object,
default: null,
},
value: {
required: false,
type: Array,
default: null,
},
list: {
required: false,
type: Array,
default: null,
},
child: {
type: Boolean,
default: false,
required: false,
},
},
computed: {
dragOptions() {
return {
animation: 0,
disabled: false,
ghostClass: "row",
group: "row",
};
},
realValue() {
return this.value ? this.value : this.list;
},
},
methods: {
emitter(value) {
this.$emit("input", value);
},
onChange: function () {
if (this.child === true) {
this.$emit("change");
} else {
this.emitter(this.value);
}
},
},
};
</script>
<style scoped></style>
PageEditor.vue:
<div id="wysiwyg-page-editor">
<ChargeOverNavBar />
<div class="editor">
<ComponentRenderer v-model="elements" :data="elements[0]" />
</div>
<ChargeOverFooter />
</div>
</template>
<script>
import ChargeOverNavBar from "#/components/ChargeOverNavBar";
import ChargeOverFooter from "#/components/ChargeOverFooter";
import InlineEditor from "#ckeditor/ckeditor5-build-inline";
import ComponentRenderer from "#/components/module-editor/ComponentRenderer";
export default {
name: "PageEditor",
components: {
ComponentRenderer,
ChargeOverFooter,
ChargeOverNavBar,
},
data() {
return {
editor: InlineEditor,
editorConfig: {},
activeSection: null,
page: {},
panels: {
pageProperties: true,
seoProperties: true,
sectionProperties: false,
},
};
},
computed: {
elements: {
get() {
console.log("getter");
return JSON.parse(JSON.stringify(this.$store.state.editor.editorData));
},
set(value) {
console.log("setter");
this.$store.dispatch("setEditor", value);
},
},
},
};
</script>
<style lang="scss" scoped>
#use "../assets/scss/components/PageEditor";
</style>
editor.js(store module):
state: {
editorData: [],
editorLoading: false,
},
mutations: {
SAVE_EDITOR(state, data) {
state.editorData = data;
},
TOGGLE_EDITOR_LOAD(state, busy) {
state.editorLoading = busy;
},
},
actions: {
setEditor({ commit }, data) {
commit("SAVE_EDITOR", data);
},
loadEditor({ commit }) {
commit("TOGGLE_EDITOR_LOAD", true);
//TODO: Change me to read API DATA
let fakeData = [
{
tagName: "section",
attributes: {
id: "section-1",
class: "test",
},
children: [
{
tagName: "div",
attributes: {
id: "container-1",
class: "container",
},
children: [
{
tagName: "div",
attributes: {
id: "row-1",
class: "row",
},
children: [
{
tagName: "div",
attributes: {
id: "col-1",
class: "col",
},
children: [],
},
{
tagName: "div",
attributes: {
id: "col-2",
class: "col",
},
children: [
{
tagName: "p",
attributes: {
id: "p-1",
class: "p",
},
textNode: "This is my paragraph",
children: [],
},
],
},
],
},
],
},
],
},
];
commit("SAVE_EDITOR", fakeData);
commit("TOGGLE_EDITOR_LOAD", false);
},
},
getters: {
getEditorData: (state) => state.editorData,
getEditorLoading: (state) => state.editorLoading,
},
};
It seems only dragging labels works for moving stuff around but not like i'd expect. I think this makes sense but why can't I drag the body anywhere? It's not slotted in header or footer and the docs says that's the onlytime it wouldn't be? Can I not use as the tag itself and drag it?
As I'm sure all of you can deduce the behavior I'm expecting, anything should be draggable into any section(in the future I want this to change so only cols can be dragged into rows, rows can only be dragged into sections etc(but for now I'm not sure how to do this so we start at the beginning :D)).
Also yes I know those components are kinda messy atm, until I fix this I'm not cleaning them up as I keep drastically changing the contents of these files trying to make it work, sorry it's hacky atm!
Any help or ideas would be amazing!
Thanks guys!
I'm sorry for my poor English.
I want to Create Spacing between Graphs and Legends in Chart.js.
I would appreciate it if you could help me.
The code is as follows.
ChartBox.vue
<template>
<Chart :chartData="chartItems" :options="chartOptions"/>
</template>
<script>
import Chart from './ChartBox/ChartBox.js'
export default {
components: {
Chart
},
data() {
return {
chartItems: {
labels: ["12月", "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月"],
datasets: [{
label: "月ごとの数",
data: [9500, 12000, 19000, 15000, 9500, 5000, 10000, 14000, 9500, 8000, 4500, 7000],
backgroundColor: 'lightblue'
}]
},
chartOptions: {
maintainAspectRatio: false,
scales: {
yAxes: [{
display: true,
position: 'right',
ticks: {
beginAtZero: true,
},
}]
}
}
}
}
}
</script>
ChartBox.js
import { Bar } from 'vue-chartjs'
export default {
extends: Bar,
props: ["chartData", "options"],
mounted() {
this.renderChart(this.chartData, this.options)
}
}
There is no built in way. What you can do is disable the legend and use the generateLabels function to get al the labels and make a custom legend https://www.chartjs.org/docs/latest/configuration/legend.html#legend-label-configuration
I am working on a project where I am implementing some charts from the Vue-Chartjs library. I need the Y-axis max value to change everytime the user changes the filters given. I Import an existing barchart from the vue-chartjs library. In the code there is a javascript file that has some defaults already, to set extra options I can use the extraOptions object as a prop to personalize each chart accordingly. Here is the default component:
import { Bar } from 'vue-chartjs'
import { hexToRGB } from "./utils";
import reactiveChartMixin from "./mixins/reactiveChart";
let defaultOptions = {
tooltips: {
tooltipFillColor: "rgba(0,0,0,0.5)",
tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
tooltipFontSize: 14,
tooltipFontStyle: "normal",
tooltipFontColor: "#fff",
tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
tooltipTitleFontSize: 14,
tooltipTitleFontStyle: "bold",
tooltipTitleFontColor: "#fff",
tooltipYPadding: 6,
tooltipXPadding: 6,
tooltipCaretSize: 8,
tooltipCornerRadius: 6,
tooltipXOffset: 10,
},
legend: {
display: false
},
scales: {
yAxes: [{
ticks: {
fontColor: "#9f9f9f",
fontStyle: "bold",
beginAtZero: true,
display: false,
min: 0,
max: 100
},
gridLines: {
display: false,
drawBorder: false,
}
}],
xAxes: [{
gridLines: {
display: false,
drawBorder: false,
},
}],
}
};
export default {
name: 'BarChart',
extends: Bar,
mixins: [reactiveChartMixin],
props: {
labels: {
type: [Object, Array],
description: 'Chart labels. This is overridden when `data` is provided'
},
datasets: {
type: [Object, Array],
description: 'Chart datasets. This is overridden when `data` is provided'
},
data: {
type: [Object, Array],
description: 'Chart.js chart data (overrides all default data)'
},
color: {
type: String,
description: 'Chart color. This is overridden when `data` is provided'
},
extraOptions: {
type: Object,
description: 'Chart.js options'
},
title: {
type: String,
description: 'Chart title'
},
},
methods: {
assignChartData() {
let { gradientFill } = this.assignChartOptions(defaultOptions);
let color = this.color || this.fallBackColor;
return {
labels: this.labels || [],
datasets: this.datasets ? this.datasets : [{
label: this.title || '',
backgroundColor: gradientFill,
borderColor: color,
pointBorderColor: "#FFF",
pointBackgroundColor: color,
pointBorderWidth: 2,
pointHoverRadius: 4,
pointHoverBorderWidth: 1,
pointRadius: 4,
fill: true,
borderWidth: 1,
data: this.data || []
}]
}
},
assignChartOptions(initialConfig) {
let color = this.color || this.fallBackColor;
const ctx = document.getElementById(this.chartId).getContext('2d');
const gradientFill = ctx.createLinearGradient(0, 170, 0, 50);
gradientFill.addColorStop(0, "rgba(128, 182, 244, 0)");
gradientFill.addColorStop(1, hexToRGB(color, 0.6));
let extraOptions = this.extraOptions || {}
return {
...initialConfig,
...extraOptions,
gradientFill
};
}
},
mounted() {
this.chartData = this.assignChartData({});
this.options = this.assignChartOptions(defaultOptions);
this.renderChart(this.chartData, this.options, this.extraOptions);
}
}
I use this js file to import the bar chart inside a vue component like you see down below.
everytime the input of the form changes i need to re render the chart. I use the onInputChange() method to turn the boolean loaded to false and call the loadData() method.
Inside the loadData() method I make an axios request that gets me the right data every time. I also get the maximum value for my Y axis.
Then in the response I call on updateChart() so that I can update the data and the max value of the chart. then i turn the boolean loaded to true again so that my chart renders accordingly.
The problem with this approach is that the chart disappears completely for a split of a second. Before deciding to change the max Value of the Y axis I was able to update the data of my chart without having to use the v-if="loaded".
I need to find a solution where the chart re renders without it completely disappearing from the page. I know some suggested to use computed variables but i don't fully understand how it is supposed to work. Here is the component minus the form fields.
I guess in it's essence what I want is to update the Y axis max value without having to re render the entire chart.
<template>
<div>
<BarChart v-if="loaded" :labels="chartLabels"
:datasets="datasets"
:height="100"
:extraOptions="extraOptions"
>
</BarChart>
<br>
</div>
</template>
<script>
import BarChart from '../../components/Library/UIComponents/Charts/BarChart'
import Dropdown from "../../components/Library/UIComponents/Dropdown"
import GroupedMultiSelectWidget from "~/components/widgets/GroupedMultiSelectWidget"
import SelectWidget from "../../components/widgets/SelectWidget";
export default{
name: 'PopularChart',
components: {BarChart, Dropdown, SelectWidget, GroupedMultiSelectWidget},
data(){
return {
loaded:true,
form:{
day: 'Today',
workspace:'',
machine_family: [],
duration: [],
user_group: [],
dt_start:'',
dt_end:''
},
url: `/api/data_app/job_count_by_hour/`,
chart_data: [],
days: [ {day:"Today", id:"Today"},
{day:"Monday", id:"0"},
{day:"Tuesday",id:"1"},
{day:"Wednesday",id:"2"},
{day:"Thursday",id:"3"},
{day:"Friday",id:"4"},
{day:"Saturday",id:"5"},
{day:"sunday",id:"6"} ],
chartLabels: ["00u", "1u", "2u", "3u","4u","5u", "6u", "7u", "8u", "9u", "10u", "11u", "12u", "13u", "14u", "15u","16u", "17", "18u","19u","20u","21u","22u","23u"],
datasets: [],
maximumValue: '',
extraOptions:{}
}
},
methods: {
onInputChange() {
this.loaded = false
this.loadData()
},
async loadData() {
await this.$axios.get(`${this.url}?day=${this.form.day}&date_start=${this.form.dt_start}&date_end=${this.form.dt_end}&workspace=${this.form.workspace}&user_group=${this.form.user_group}&machine_family=${this.form.machine_family}`)
.then(response => {
this.updateChart(response.data.results,response.data.maximum)
this.loaded = true
})
},
updateChart(data,maxValue) {
this.datasets = [{
label: ["jobs %"],
backgroundColor:"#f93232",
data: data
},]
this.maximumValue = maxValue
this.extraOptions = {
tooltips: {
callbacks:{
label: function (tooltipItems,){
if (tooltipItems.value > ((50/100) * maxValue)){
return 'busy';
}else if (tooltipItems.value < ((30/ 100) * maxValue) ){
return ' not busy';
}else if ( tooltipItems.value < ((40/ 100) * maxValue )){
return 'kind of busy'
}
}
}
},
scales: {
yAxes: [{
gridLines: {
zeroLineColor: "transparent",
display: false,
drawBorder: false,
},
ticks: {
max: this.maximumValue,
display: true,
}
}],
xAxes: [{
gridLines: {
zeroLineColor: "transparent",
display: false,
drawBorder: false,
},
}],
},
}
},
},
mounted() {
this.loadData()
},
}
</script>
You can bind the chart to a variable in data(), initialize it with some defaults in mounted() and whenever you want to update the chart data, you use the Chart.js API.
An implementation could look something like this:
export default {
data() {
return {
chart: null
};
},
mounted() {
this.chart = new Chart(/* defaults */);
},
methods: {
updateChart(data) {
this.chart.data.datasets = data;
/* rest of the chart updating */
this.chart.update();
}
}
};
I have a vue component that implements vue-chartjs's HorizontalBar component that I render as a cell in a bootstrap-vue table. My desire is to have the chart maintain a fixed height but scale horizontally as the window grows/shrinks.
The chart plays nicely enough when the page is first loaded but grows uncontrollably on re-size to cover other elements on the page.
How do I make them properly responsive without them going where they shouldn't? I'd also like to make sure they actually take up the extent of the table cell they're in, rather than weirdly stopping a few pixels short.
Here's my relevant component code:
<script>
import { HorizontalBar } from "vue-chartjs";
import Constants from "../../services/Constants";
export default {
name: "ResponseGraph",
extends: HorizontalBar,
props: { /* some props */ },
data: () => ({
chartData: {},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
xAxes: [{ stacked: true, display: false }],
yAxes: [{ stacked: true, display: false }]
},
legend: {
display: false
},
tooltips: {
enabled: false
}
}
}),
mounted() {
this.chartData = {
datasets: [
// some data
]
};
this.renderChart(this.chartData, this.options);
}
};
</script>
And how I'm using it inside of my table code:
<template v-slot:cell(graph)="data">
<response-graph
:correct="data.item.correct"
:incorrect="data.item.incorrect"
:omitted="data.item.omitted"
:height="20"
/>
</template>
In my react native application I have a Highcharts chart displaying some bar chart info.
I want to display the legend, so people know what the chart is showing, but I want to disable the functionality that hides bars when the legend is touched.
Additionally the bars themselves fade in and out when you press them... I have disabled the 'tooltip' but the fading still happens, how can I stop this?
If I could render to a static image that would be ideal!
just for info the code is
let Highcharts = "Highcharts";
const conf = {
chart: {
type: "column",
animation: false,
marginRight: 10,
dateFormat: "dd/mm/YYYY"
},
title: {
text: "Spread Events"
},
xAxis: {
type: "datetime",
tickPixelInterval: 50
},
yAxis: {
title: {
text: "Spread"
},
plotLines: [
{
value: 0,
width: 1,
color: "#808080"
}
]
},
tooltip: { enabled: false },
legend: {
enabled: true
},
exporting: {
enabled: false
},
plotOptions: {
column: {
pointPadding: 0.2,
borderWidth: 0
}
},
series: FieldStore.graphData.slice()
};
To disable hiding series on legend click, return false from legendItemClick event function.
To disable a tooltip and the fading effect on series hover set enableMouseTracking to false. If you want to also turn off the fading effect on legend hover, change opacity in inactive state:
plotOptions: {
series: {
enableMouseTracking: false,
states: {
inactive: {
opacity: 1
}
},
events: {
legendItemClick: function() {
return false;
}
}
}
}
Live demo: http://jsfiddle.net/BlackLabel/gjkprbto/
API Reference:
https://api.highcharts.com/highcharts/series.bar.enableMouseTracking
https://api.highcharts.com/highcharts/series.bar.events.legendItemClick
https://api.highcharts.com/highcharts/series.bar.states.inactive.opacity