Preserve line chart ornaments when you override the click event in dc.js? - data-visualization

Still working on this visualization: https://ayyrickay.github.io/circulating-magazines/
I've got some good updates going, but one thing that's irking me is that, when somebody clicks on the line chart, it maintains state. For example, if I click on an issue, it runs this code to update both application state and crossfilter state
(chart) => {
chart.selectAll('circle').on('click', (selected) => {
state.circulationClicked = true
const clearFilterButton = document.getElementById('clearIssueFilterButton')
clearFilterButton.classList.remove('hide')
clearFilterButton.addEventListener('click', lineChart.unClick)
renderIssueData(selected)
samplePeriodEnd.filter(d => {
const currentIssueDate = moment.utc(selected.x)
const periodEnding = moment.utc(d)
const periodStart = moment.utc({'year': periodEnding.get('year'), 'month': periodEnding.get('month') === 5 ? 0 : 6, 'day':1})
if (currentIssueDate >= periodStart && currentIssueDate <= periodEnding) {
Object.assign(state, {currentIssueDate, periodStart, periodEnding})
return currentIssueDate >= periodStart && currentIssueDate <= periodEnding
}
})
state.totalSalesByState = salesByState.all().reduce((a, b) => ({value: {sampled_total_sales: a.value.sampled_total_sales + b.value.sampled_total_sales}}))
us1Chart.colorDomain(generateScale(salesByState))
us1Chart.customUpdate()
})
}
There's a lot going on here, but I'm frustrated because it seems like because I've overridden the default click, I've blown out some functionality that I wanted - specifically, I want the circle associated with the data point to stay in the viz.
Using the renderDataPoints method isn't right, because I only need the circle to stick around on click. I've also tried creating a moveToFront method to bring the circle to the front, but that hasn't worked for me. My problem seems to be that I'm not able to locate the circle and modify its properties in the click method itself. I get the data, but I don't get the HTML/SVG associated with the data point to modify it accordingly.
Rambly question as always, but would love some help sorting this out (and potentially getting it documented somewhere?)

Related

How do I make the observe function work more than once?

I'm trying to create a slider that will update every time a slide is altered. I've got it set to observe, observeParents, and observeChildren, and it works the first time I show or hide slides, but it's only working once. Is there a good workaround for this?
Here's a link to the page with the slider on it. Click the color swatches to see what I'm talking about. Thanks in advance!
I figured out a workaround! I added a data-element called data-slides that updates on the container when you click to make the slideshow change, like so.
thumbnails.each(function() {
var wrapper = $(this);
container = $('.swiper-container');
color = colorLabel.val();
while (wrapper.parent().children().length === 1) {
wrapper = wrapper.parent();
}
if (jQuery(this).attr('alt') === optionValue) {
wrapper.fadeIn('500').show();
container.attr('data-slides', color);
} else {
wrapper.fadeOut('500').hide();
}
});
It triggers the observe function and allows it to update as necessary.

DatePicker is working really weird in iOS react-native 0.60.6

Don't know when this happened because it used to work before, but it might be probably ever since I upgraded react-native to 0.60.6.
I tested 3 libraries thus far, and it's happening in all of them. Currently using react-native-woodpicker.
This is how it looks, it's all messed up:
Even though it shows no value where i'm standing at, there really is, you can literally feel the vibration when swiping up/down, but most of the times there is no value, you can swipe for like 10 seconds straight and all of a sudden the list of values will show up.
It's happening in all the DatePickers within my app, and only in iOS, Android works fine. So there seems to be an issue with DatePickerIOS.
Some code:
<DatePicker
onDateChange={this.handleDatePicker}
date={new Date(this.state.user.dateOfBirth)}
title={i18n.t('dateOfBirth')}
placeholder={this.handleDatePickerPlaceholder()}
placeholderStyle={scopedStyles.datePickerText}
iosPickerMode="date"
androidPickerMode="spinner"
minDate={minDate}
maxDate={new Date()}
/>
handleDatePicker = data => {
var {user} = this.state;
var thisUser = user;
var date = this.getObjectFromDate(data);
thisUser.dateOfBirth = data;
this.setState({
user: thisUser,
dateObject: date
});
};
getObjectFromDate(date) {
return {
day: date.getDate(),
month: (date.getMonth()) + 1,
year: date.getFullYear()
}
};
handleDatePickerPlaceholder() {
const {dateObject} = this.state;
if (Object.keys(dateObject).length) {
return `${dateObject.day}/${dateObject.month}/${dateObject.year}`
} else {
return `DD/MM/YYYY`;
}
}
Any help given is appreciated.
If anyone is under the same problem, it's due to iOS 13's dark mode, thanks to this guy on github, I was able to solve it.
The way to solve it is to add a new key/string value within info.plist file in your iOS app.
<key>UIUserInterfaceStyle</key>
<string>Light</string>
Hope this helps to others.

How to access or get value of specific graph on chart plot by click event?

I use vue-chartjs to draw some chart like line, bar, etc.
In my project, there are many cases using specific value or lable of data in chart.
Using tooltip option of vue-chartjs, I can check that value or label of data item when hovered.
I want to know how to access or get information of specific data matched with point on graph when clicked(not hovered).
Here is my code about chart options.
chartOptions: {
responsive: false,
onClick: function(evt){
//Some logic to get value of label of specific data...(type, label, value, ...)
}
In my case, I use 'onclick' option to access specific data on point triggered 'click' event. In 'onClick' callback, I checked all of chart elements and dataset, etc.
How can I get value of label specific dataItem on point of graph(like line) or bar of graph(like bar) when triggered click event?
I was not able to find a solution that worked for me, but I dug a little bit and this is what I came up with.
onClick: function(evt, array) {
if (array.length != 0) {
var position = array[0]._index;
var activeElement = this.tooltip._data.datasets[0].data[position]
console.log(activeElement);
} else {
console.log("You selected the background!");
}
}
This will get the position in the array that you clicked and grab the data from what position you clicked. This may not be the prettiest or best example, but it worked for me.
This solution use the getElementAtEvent method of chartjs, but to use that you need reference to the Chart itself, not the Vue component. We can get that from the $data._chart property. To use this in a parent Vue component, we use the $refs as seen below`.
So parent defines the chart options
{
...
options: {
onClick: this.handleChartClick
}
...
}
and then parent method, using $refs with $data._chart to get the chart. We get the datasetIndex and value and also the tooltip
handleChartClick(evt, elements) {
var chart = this.$refs.periodChart.$data._chart;
const chartIndex = chart.getElementAtEvent(evt);
if (chartIndex.length !== 0) {
const datasetIndex = chartIndex[0]._datasetIndex;
const position = chartIndex[0]._index;
const info = {
datasetIndex: datasetIndex,
valueIndex: position,
label: chart.tooltip._data.labels[position],
value: chart.tooltip._data.datasets[datasetIndex].data[position]
};
console.log(info);
} else {
console.log("Background clicked");
}

Hiding a series by default in a spider plot

I have a spider plot in using the graphing library of Dojo defined like this:
require([
"dojox/charting/Chart",
"dojox/charting/themes/Claro",
"dojox/charting/plot2d/Spider",
"dojox/charting/action2d/Tooltip",
"dojox/charting/widget/SelectableLegend",
"dojox/charting/axis2d/Default"
], function (Chart, theme, Spider, Tooltip, Legend, Default) {
var chart = new Chart(element).setTheme(theme).addPlot("default", {
type: Spider,
radius: 200,
fontColor: "black",
labelOffset: "-20"
});
var colors = ["blue", "red", "green", "yellow", "purple", "orange", "teal",
"maroon", "olive", "lime", "aqua", "fuchsia"];
$.each(factors, function (index, factor) {
chart.addAxis(factor.name, {
type: Default,
min: factor.min,
max: factor.max
});
});
$.each(presets, function (pIndex, preset) {
var data = [];
$.each(factors, function (fIndex, factor) {
data[factor.name] = preset.values[fIndex];
});
chart.addSeries(preset.short, data, {
fill: colors[pIndex % colors.length]
});
});
new Tooltip(chart, "default");
chart.render();
new Legend({
chart: chart,
horizontal: false
}, $(element).next(".legend")[0]);
});
I add a series for every member of an array called presets and I use a selectable legend that lets the user turn them on or off as they want. However, what I can't seem to find in the docs is how to start a series in the unselected, not visible state? What I ideally want to do is cap the number of series visible when the page loads because in some cases I have up to 14 presets and it just looks a mess until the user deselects a bunch. So I'd like to have, say, every preset above the first 5 be hidden at the start.
Here's a crude fiddle I've knocked to demonstrate. What I want is to have some of the series unselected when the plot is first displayed.
Update: I tried adding this after adding my series:
var checkboxes = $(".dijitCheckBoxInput").each((index, elem) => {
if (index > 4) {
elem.click();
}
});
Which works, but seems very fragile. If they change the class assigned to checkboxes, it'll break. Also, it prohibits me using more than one set of dojo checkboxes because I don't have a good way to tell the difference. (Note, the IDs of the checkboxes added by the SelectableLegend are dijit_form_CheckBox_0, dijit_form_CheckBox_1, etc, which also gives no useful information as to what they are related to). I thought I might be able to use the legend placeholder div as a way to select the descendant checkboxes, but it appears that Dojo replaces the placeholder entirely with a table.
i looked into the dojo code and found the area in which the shapes are toggled on & off whitin the SelectableLegend.js :
var legendCheckBox = query(".dijitCheckBox", legend)[0];
hub.connect(legendCheckBox, "onclick", this, function(e){
this._toggle(shapes, i, legend.vanished, originalDyn, seriesName, plotName);
legend.vanished = !legend.vanished;
e.stopPropagation();
});
The toggling process is very complex and is based on many local attributes:
_toggle: function(shapes, index, isOff, dyn, seriesName, plotName){
arrayUtil.forEach(shapes, function(shape, i){
var startFill = dyn.fills[i],
endFill = this._getTransitionFill(plotName),
startStroke = dyn.strokes[i],
endStroke = this.transitionStroke;
if(startFill){
if(endFill && (typeof startFill == "string" || startFill instanceof Color)){
fx.animateFill({
shape: shape,
color: {
start: isOff ? endFill : startFill,
end: isOff ? startFill : endFill
}
}).play();
}else{
shape.setFill(isOff ? startFill : endFill);
}
}
if(startStroke && !this.outline){
shape.setStroke(isOff ? startStroke : endStroke);
}
}, this);
}
I tried also checking & unchecking the dijit/form/Checkbox in a legend manually, but that does not trigger the _toggle function in any case, even if you do a render() / fullrender() on the chart.
With that in mind it seems that there is no other possibilty to toggle the series on and off than by firing the onclick events manually.
To make your code less fragile, you could access the Checkbox widgets within the legend manually using:
query(".dijitCheckBox", legend); // Should deliver an array containing
the widgets.
and triggering the onclick event on them. Their keynumber in the array should correspond to the order the series where added...
Dojo is a fine piece of work, please dont stop working with it !
dojox/charting/Series has an attribute called dirty which according to the API docs is a "flag indicating whether or not this element needs to be rendered".
Alternately, if you are limiting the display of some series you can write a separate interface for adding them. For example, loop over the first 5. Then create a select box or list of check boxes with all entries and an onchange event that calls chart.addSeries.
Keeping a reference to each series you create will allow you to later call destroy() or destroyRecursive() on it if the user no longer wishes it displayed.
So while ideally you could toggle the display of these series, the worst case senerio is that you just add, destroy, and read based on some user input.
Using a templated widget will allow you to keep this interface and the chart tightly linked and support reuse.
BTW, consider using "dojo/_base/array" and "dojo/query" in place of the jquery
I think i've got it !
I found another way to access the checkboxes ! It's the same way dojo uses internally to connect the "toggle code" to the onclick event. First take a look at this from SelectableLegend.js (Lines 150 - 156):
// toggle action
var legendCheckBox = query(".dijitCheckBox", legend)[0];
hub.connect(legendCheckBox, "onclick", this, function(e){
this._toggle(shapes, i, legend.vanished, originalDyn, seriesName, plotName);
legend.vanished = !legend.vanished;
e.stopPropagation();
});
It looks like they use the ".dijitCheckBox" class to find the checkbox dom element and connect to it using dojo/connect. Now based on that, i made this function:
function toggleSeries (legend,num) {
dojo.query("*",legend.legends[num])[0].click();
dijit.findWidgets(legend.legends[num])[0]._onClick(); }
It doesn't use any class definition (because of the *) and it accesses the areas where the checkboxes are from within the SelectableLegend. It needs the SelectableLegend and the number of the series you want to deactivate as parameters. Here the jsfiddle example with this function & hiding all 4 of your series with it:
http://jsfiddle.net/luciancd/92Dzv/17/
Also please notice the "onDomReady" Option in jsfiddle, without it: doesnt work in IE.
And the ready function within the code !
Lucian
I have updated your code http://jsfiddle.net/92Dzv/18/
Here is the key to toogle.
dom.byId(le._cbs[0].id).click();
dom.byId(le._cbs[2].id).click();
Choose the index of your legend and set to _cbs.
By this way le._cbs[0].id you will get the real id of checkbox (that inside in the widget) and then just use click()
Note : le is came from here.
var le = new Legend({
chart: chart,
horizontal: false
}, legend);

How to add an image to an item in a dojo/dnd/Source

I've create a dnd solution, with a source and target location. Right now the drag is bi-directional, I would like to have it be one-direction target to source. Then from the source add a image to each item (a delete icon), so that the users can then click the icon and send the record back to the correct target.
Part 1, I am trying to understand how to make the dnd one-directional and Part 2, how do I add an image to each item.
Thanks
There are events which handles this in the dnd module.
as for 'part1', see this http://dojotoolkit.org/reference-guide/1.8/dojo/dnd.html#id6 and set is'Source : false on the target which should only be a target.
and with 'part2' go to http://dojotoolkit.org/reference-guide/1.8/dojo/dnd.html#id8 and read about 'onDrop'. If overloading that function in your 'target-only' source, you will gain access to the nodes which is being dropped.
onDrop: function(source, nodes, copy) {
dojo.forEach(nodes, function(node) {
node.innerHTML += "<button title=Delete> X </button>";
});
}
This is what I got working.
dojo.connect(selectedInstructions, "onDndDrop", instructions.addDeleteButton);
addDeleteButton: function (source, nodes, copy, target) {
if (source != target) {
dojo.forEach(nodes, function(node) {
var instructionId = node.getAttribute("id");
var oImg = document.createElement("img");
oImg.setAttribute('src', 'images/delete.png');
oImg.setAttribute('alt', 'Remove');
oImg.setAttribute('class', 'remove_instruction');
oImg.setAttribute('onClick', "javascript:instructions.removeInstruction('" + instructionId + "')");
document.getElementById(instructionId).appendChild(oImg);
});
}
},
I then tried to get on to work, since connect is being depreciated, but I didn't appear to have much luck. I will have to come back to it at a later date, as I am on a time crunch right now to get this code out.
on(selectedInstructions, "onDrop", instructions.addDeleteButton);
aspect.after(selectedInstructions, "onDrop", instructions.addDeleteButton);
Wish the Dojo documentation was better. Thanks goodness for the community and it's support though.