pepstock-org / Charba

J2CL and GWT Charts library based on CHART.JS
https://pepstock-org.github.io/Charba-Wiki/docs
Apache License 2.0
62 stars 6 forks source link

Support for floating bar chart #55

Closed rr22x closed 4 years ago

rr22x commented 4 years ago

Since Charts.js 2.9.0 it is possible to use floating bar charts. So you can use data points like [yStart, yEnd]. yStart defines the start point of the bar (instead of 0) and yEnd the end point accordingly. Is it possible to use these kind of datapoints in charba?

see https://github.com/chartjs/Chart.js/pull/6056

stockiNail commented 4 years ago

@rr22x this is not implemented yet. It's in the list of implementations going to Chart.JS v3.

But let me try a workaround (even if it could not be "elegant").

stockiNail commented 4 years ago

@rr22x any workaround is not possible, sorry. It will be implemented. Thks!

stockiNail commented 4 years ago

@rr22x let me share with you a first results and let me know if you see something not correct.

The floating data for Bar datasets has been implemented. We are doing the tests The timeseries (by DataPoint and TimeSeriesItem) are still missing.

Here is the code of a sample:

BarChartWidget chart = new BarChartWidget();
chart.getOptions().setResponsive(true);
chart.getOptions().getLegend().setPosition(Position.TOP);
chart.getOptions().getTitle().setDisplay(true);
chart.getOptions().getTitle().setText("Bar chart");

BarDataset dataset1 = chart.newDataset();
dataset1.setLabel("dataset 1");

IsColor color1 = GoogleChartColor.values()[0];

dataset1.setBackgroundColor(color1.alpha(0.2));
dataset1.setBorderColor(color1.toHex());
dataset1.setBorderWidth(1);

// -------------
// creates Floating DATA
// -------------
double[] values = getRandomDigits(months);
double[] gaps = getRandomDigits(months, false);

List<FloatingData> data = new LinkedList<>();
for (int i=0; i<months; i++) {
    data.add(new FloatingData(values[i], values[i] + gaps[i]));
}
dataset1.setFloatingData(data);
// -------------

chart.getData().setLabels(getLabels());

chart.getData().setDatasets(dataset1);

bug55

More in the next days.

rr22x commented 4 years ago

That looks very good! exactly what i need

stockiNail commented 4 years ago

@rr22x the implementation of floating bars has been merged into master.

We are now working on timeseries. If you need it urgently, feel free to clone (or fetch) the master and compile Charba. We decided to release new version with this feature and also with new annotation plugin, where new options has been released.

But of course we need time to release it (doc, showcases for J2CL and GWT where we are preparing some use cases with new features).

stockiNail commented 4 years ago

@rr22x let me share with you that floating bars are not working on time scales, at least on examples I did. See CHART.JS issue #7356 where I have asked more info about that. Waiting for a feedback (I think we need Chart.JS v3), we stooped further development on time series.

rr22x commented 4 years ago

@stockiNail Many thanks for the feedback. So I tested out the new feature. It works! In my usecase the timeline feature is not mandatory as I create and set the time labels myself. However, I watch the issue with interest.

floating_bar

stockiNail commented 4 years ago

@rr22x fine! About the implementation, we have to check how Labels and DataLabels plugins are working with floating bars. There are some callbacks where a double is passed as value but I think an array should be passed in case of floating bars. This last case is not ready. Let me use this issue to share also this updates on plugins before closing it, if is ok for you.

rr22x commented 4 years ago

@stockiNail ok. no problem. i also work with the DataLabels plugin. Maybe i can give feedback

stockiNail commented 4 years ago

Would be great! I didn't have time to test it yet (I'm gonna do later this afternoon) but I think the issue could be on DataLabels formatting callback. First, I need to check if DataLabels works with floating bars. Second, if the object passed in case of floating bars is an array (what I expect) or other and then to prepare an interface to implement for formatting callback.

rr22x commented 4 years ago

@stockiNail ok i tested it with the formatting callback. there is no exception or something, but apparently the formatter is called twice - for the start and the end value seperately

stockiNail commented 4 years ago

@rr22x Thank youuuu! I think it could be acceptable even if, from my mindset, I'd would prefer to call callback once with the array. What do you think?

rr22x commented 4 years ago

@stockiNail yes i also think one callback is sufficient. Especially in my usecase i have to display both values in one label.

data_label_low_high

stockiNail commented 4 years ago

@rr22x first of all, the feature on time series is already implemented in CHART.JS and it will deliver in v3. Nevertheless it's also possible to use floating bars on time scale with version 2.9.3 by a workaround (an example will be available in showcase).

About the callback, we're gonna start thinking, designing and developing the interface for a single callback. Thank you very much!

stockiNail commented 4 years ago

@rr22x we have committed into master the floating bars implementation. We have changed also the DataLabels plugin callback for formatting in order to manage whatever data type of datasets (doubles, string and arrays of doubles).

The same has been been done for Labels Plugin, the intercafe remains the same but the RenderItem is changed in order to manage whatever data type of datasets (doubles, string and arrays of doubles).

We have also created some test/use cases for floating bars. They will be committed soon on both shocase projects.

Let us know how is going with this feature.

stockiNail commented 4 years ago

@rr22x floating bars + DataLabels sample.

bug551

Code:

BarChartWidget chart = new BarChartWidget();
chart.getOptions().setResponsive(true);
chart.getOptions().getLegend().setDisplay(false);
chart.getOptions().getTooltips().setEnabled(false);
chart.getOptions().getLayout().getPadding().setTop(32);
chart.getOptions().getElements().getLine().setFill(false);

chart.getOptions().getPlugins().setEnabled(DefaultPlugin.LEGEND, false);
chart.getOptions().getPlugins().setEnabled(DefaultPlugin.TITLE, false);

BarDataset dataset1 = chart.newDataset();
dataset1.setLabel("dataset 1");

IsColor color1 = GoogleChartColor.values()[0];

dataset1.setBackgroundColor(color1.toHex());
dataset1.setBorderColor(color1.toHex());

double[] values = getRandomDigits(months);
double[] gaps = getRandomDigits(months, false);

double[][] dataToSet = new double[months][2];
for (int i=0; i<months; i++) {
    dataToSet[i] = new double[] {values[i], values[i] + Math.max(gaps[i], 20)};
}
dataset1.setFloatingData(dataToSet);

CartesianCategoryAxis axis1 = new CartesianCategoryAxis(chart);
axis1.setDisplay(true);
axis1.setOffset(true);

CartesianLinearAxis axis2 = new CartesianLinearAxis(chart);
axis2.setDisplay(true);
axis2.getTicks().setBeginAtZero(true);
axis2.getScaleLabel().setDisplay(true);
axis2.getScaleLabel().setLabelString("Value");

chart.getOptions().getScales().setXAxes(axis1);
chart.getOptions().getScales().setYAxes(axis2);

chart.getData().setLabels(getLabels());
chart.getData().setDatasets(dataset1);

DataLabelsOptions top = new DataLabelsOptions();
top.setAlign(Align.CENTER);
top.setAnchor(Anchor.END);
top.setBackgroundColor(HtmlColor.WHITE);
top.setColor(HtmlColor.GREEN);
top.setBorderColor(color1);
top.setBorderRadius(25);
top.setBorderWidth(2);
top.setDisplay(Display.AUTO);
top.getFont().setWeight(Weight.BOLD);
top.setFormatter(new FormatterCallback() {

    @Override
    public String invoke(IsChart chart, DataItem dataItem, ScriptableContext context) {
        return Utilities.applyPrecision(dataItem.getValueAsFloatingData().getEnd(), 0);
    }

});

DataLabelsOptions down = new DataLabelsOptions();
down.setAlign(Align.CENTER);
down.setAnchor(Anchor.START);
down.setBackgroundColor(HtmlColor.WHITE);
down.setColor(HtmlColor.RED);
down.setBorderColor(color1);
down.setBorderRadius(25);
down.setBorderWidth(2);
down.getFont().setWeight(Weight.BOLD);
down.setFormatter(new FormatterCallback() {

    @Override
    public String invoke(IsChart chart, DataItem dataItem, ScriptableContext context) {
        return Utilities.applyPrecision(dataItem.getValueAsFloatingData().getStart(), 0);
    }

});

DataLabelsOptions option = new DataLabelsOptions();
option.getLabels().setLabel("top", top);
option.getLabels().setLabel("down", down);

chart.getOptions().getPlugins().setOptions(DataLabelsPlugin.ID, option);
rr22x commented 4 years ago

@stockiNail wow that's fantastic!