Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add horizontal bar chart #5154

Merged
merged 37 commits into from
Oct 15, 2020
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
052eadc
added bar chart boilerplate
rafawendel Sep 6, 2020
8b57801
added x/y manipulation
rafawendel Sep 6, 2020
0a591c4
replaced x/y management to inner series preparer
rafawendel Sep 6, 2020
07d252b
added tests
rafawendel Sep 7, 2020
0bb4e85
moved axis inversion to all charts series
rafawendel Sep 7, 2020
53ee4b0
removed line and area
rafawendel Sep 7, 2020
0995bf3
inverted labels ui
rafawendel Sep 7, 2020
d26989e
removed normalizer check, simplified inverted axes check
rafawendel Sep 7, 2020
c1c78a8
finished working hbar
rafawendel Sep 8, 2020
104e830
minor review
rafawendel Sep 8, 2020
0e4db11
added conditional title to YAxis
rafawendel Sep 8, 2020
8346519
generalized horizontal chart for line charts, resetted state on globa…
rafawendel Sep 8, 2020
a753a0a
fixed updates
rafawendel Sep 9, 2020
945ea53
fixed updates to layout
rafawendel Sep 9, 2020
1e2d3ba
fixed minor issues
rafawendel Sep 9, 2020
e430c2a
removed right Y axis when axes inverted
rafawendel Sep 9, 2020
f6101cf
ran prettier
rafawendel Sep 9, 2020
0fd5a3a
fixed updater function conflict and misuse of getOptions
rafawendel Sep 11, 2020
d4dd4b9
renamed inverted to swapped
rafawendel Oct 14, 2020
7b1f6b2
created mappingtypes for swapped columns
rafawendel Oct 14, 2020
0c34ef5
removed unused import
rafawendel Oct 14, 2020
47aace8
Merge branch 'master' into add-horizontal-bar-chart
rafawendel Oct 14, 2020
601ef53
minor polishing
rafawendel Oct 14, 2020
089d1b9
improved series behaviour in h-bar
rafawendel Oct 14, 2020
20631c2
minor fix
rafawendel Oct 14, 2020
b43ecce
added basic filter to ChartTypeSelect
rafawendel Oct 14, 2020
2a028e3
final setup of filtered chart types
rafawendel Oct 15, 2020
894dcfe
Update viz-lib/src/components/visualizations/editor/createTabbedEdito…
gabrieldutra Oct 15, 2020
857e84f
added proptypes and renamed ChartTypeSelect props
rafawendel Oct 15, 2020
4c427bb
Add missing import
kravets-levko Oct 15, 2020
24be92e
fixed import, moved result array to global scope
rafawendel Oct 15, 2020
d5cd272
Merge branch 'add-horizontal-bar-chart' of github.com:rafawendel/reda…
rafawendel Oct 15, 2020
a2e70f5
merged import
rafawendel Oct 15, 2020
7ec77b2
clearer naming in ChartTypeSelect
rafawendel Oct 15, 2020
31eaafb
better lodash map syntax
rafawendel Oct 15, 2020
d037d04
fixed global modification
rafawendel Oct 15, 2020
67fb205
moved result inside useMemo
rafawendel Oct 15, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ export function TabbedEditor({ tabs, options, data, onOptionsChange, ...restProp
return (
<Tabs animated={false} tabBarGutter={20}>
{map(tabs, ({ key, title, component: Component }) => (
<Tabs.TabPane key={key} tab={<span data-test={`VisualizationEditor.Tabs.${key}`}>{title}</span>}>
<Tabs.TabPane
key={key}
tab={<span data-test={`VisualizationEditor.Tabs.${key}`}>{isFunction(title) ? title(options) : title}</span>}>
<Component options={options} data={data} onOptionsChange={optionsChanged} {...restProps} />
</Tabs.TabPane>
))}
Expand All @@ -33,7 +35,10 @@ TabbedEditor.propTypes = {
tabs: PropTypes.arrayOf(
PropTypes.shape({
key: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
title: PropTypes.oneOfType([
PropTypes.string,
PropTypes.func, // (options) => string
]).isRequired,
isAvailable: PropTypes.func, // (options) => boolean
component: PropTypes.func.isRequired,
})
Expand Down
18 changes: 16 additions & 2 deletions viz-lib/src/visualizations/chart/Editor/ChartTypeSelect.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { map } from "lodash";
import { filter, includes, map } from "lodash";
import React, { useMemo } from "react";
kravets-levko marked this conversation as resolved.
Show resolved Hide resolved
import { Select } from "@/components/visualizations/editor";
import { visualizationsSettings } from "@/visualizations/visualizationsSettings";

export default function ChartTypeSelect(props) {
export default function ChartTypeSelect({ hiddenChartTypes, ...props }) {
const chartTypes = useMemo(() => {
const result = [
{ type: "line", name: "Line", icon: "line-chart" },
Expand All @@ -20,6 +20,10 @@ export default function ChartTypeSelect(props) {
result.push({ type: "custom", name: "Custom", icon: "code" });
}

if (hiddenChartTypes.length > 0) {
return filter(result, ({ type }) => !includes(hiddenChartTypes, type));
}

return result;
}, []);

Expand All @@ -34,3 +38,13 @@ export default function ChartTypeSelect(props) {
</Select>
);
}

ChartTypeSelect.defaultProps = {
hiddenChartTypes: [],
};

ChartTypeSelect.propTypes = {
hiddenChartTypes: PropTypes.arrayOf(
PropTypes.oneOf(["line", "area", "column", "pie", "scatter", "heatmap", "bubble", "box"])
),
};
12 changes: 10 additions & 2 deletions viz-lib/src/visualizations/chart/Editor/ColumnMappingSelect.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,17 @@ const MappingTypes = {
zVal: { label: "Color Column" },
};

export default function ColumnMappingSelect({ value, availableColumns, type, onChange }) {
const SwappedMappingTypes = {
...MappingTypes,
x: { label: "Y Column" },
y: { label: "X Columns", multiple: true },
};

export default function ColumnMappingSelect({ value, availableColumns, type, onChange, areAxesSwapped }) {
const options = sortBy(filter(uniq(flatten([availableColumns, value])), v => isString(v) && v !== ""));
const { label, multiple } = MappingTypes[type];

// this swaps the ui, as the data will be swapped on render
const { label, multiple } = !areAxesSwapped ? MappingTypes[type] : SwappedMappingTypes[type];

return (
<Section>
Expand Down
23 changes: 23 additions & 0 deletions viz-lib/src/visualizations/chart/Editor/GeneralSettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export default function GeneralSettings({ options, data, onOptionsChange }) {
onOptionsChange({
globalSeriesType,
showDataLabels: globalSeriesType === "pie",
swappedAxes: false,
seriesOptions: mapValues(options.seriesOptions, series => ({
...series,
type: globalSeriesType,
Expand All @@ -106,6 +107,15 @@ export default function GeneralSettings({ options, data, onOptionsChange }) {
}
}

function handleAxesSwapping() {
// moves any item in the right Y axis to the left one
const seriesOptions = mapValues(options.seriesOptions, series => ({
...series,
yAxis: 0,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By doing this, every time horizontal chart changes state, all series go back to left Y axis.

}));
onOptionsChange({ swappedAxes: !options.swappedAxes, seriesOptions });
}

return (
<React.Fragment>
<Section>
Expand All @@ -117,11 +127,24 @@ export default function GeneralSettings({ options, data, onOptionsChange }) {
/>
</Section>

{includes(["column", "line"], options.globalSeriesType) && (
<Section>
<Checkbox
data-test="Chart.SwappedAxes"
defaultChecked={options.swappedAxes}
checked={options.swappedAxes}
onChange={handleAxesSwapping}>
Horizontal Chart
</Checkbox>
</Section>
)}

{map(mappedColumns, (value, type) => (
<ColumnMappingSelect
key={type}
type={type}
value={value}
areAxesSwapped={options.swappedAxes}
availableColumns={unusedColumns}
onChange={handleColumnMappingChange}
/>
Expand Down
15 changes: 15 additions & 0 deletions viz-lib/src/visualizations/chart/Editor/GeneralSettings.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,4 +194,19 @@ describe("Visualizations -> Chart -> Editor -> General Settings", () => {
expect(elementExists(el, "Chart.ColumnMapping.yError")).toBeTruthy();
});
});

test("Toggles horizontal bar chart", done => {
const el = mount(
{
globalSeriesType: "column",
series: {},
},
done
);

findByTestID(el, "Chart.SwappedAxes")
.last()
.find("input")
.simulate("change", { target: { checked: true } });
});
});
38 changes: 21 additions & 17 deletions viz-lib/src/visualizations/chart/Editor/SeriesSettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,23 +39,26 @@ function getTableColumns(options, updateSeriesOption, debouncedUpdateSeriesOptio
];

if (!includes(["pie", "heatmap"], options.globalSeriesType)) {
result.push({
title: "Y Axis",
dataIndex: "yAxis",
render: (unused, item) => (
<Radio.Group
className="series-settings-y-axis"
value={item.yAxis === 1 ? 1 : 0}
onChange={event => updateSeriesOption(item.key, "yAxis", event.target.value)}>
<Radio value={0} data-test={`Chart.Series.${item.key}.UseLeftAxis`}>
left
</Radio>
<Radio value={1} data-test={`Chart.Series.${item.key}.UseRightAxis`}>
right
</Radio>
</Radio.Group>
),
});
if (!options.swappedAxes) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows the series picker to remain in the horizontal bar, which not only improves the UI, but also makes it easier to deal with state changes.

result.push({
title: "Y Axis",
dataIndex: "yAxis",
render: (unused, item) => (
<Radio.Group
className="series-settings-y-axis"
value={item.yAxis === 1 ? 1 : 0}
onChange={event => updateSeriesOption(item.key, "yAxis", event.target.value)}>
<Radio value={0} data-test={`Chart.Series.${item.key}.UseLeftAxis`}>
left
</Radio>
<Radio value={1} data-test={`Chart.Series.${item.key}.UseRightAxis`}>
right
</Radio>
</Radio.Group>
),
});
}

result.push({
title: "Type",
dataIndex: "type",
Expand All @@ -64,6 +67,7 @@ function getTableColumns(options, updateSeriesOption, debouncedUpdateSeriesOptio
data-test={`Chart.Series.${item.key}.Type`}
dropdownMatchSelectWidth={false}
value={item.type}
hiddenChartTypes={["pie", "heatmap", "bubble", "box"]}
onChange={value => updateSeriesOption(item.key, "type", value)}
/>
),
Expand Down
4 changes: 2 additions & 2 deletions viz-lib/src/visualizations/chart/Editor/YAxisSettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default function YAxisSettings({ options, onOptionsChange }) {

return (
<React.Fragment>
<Section.Title>Left Y Axis</Section.Title>
<Section.Title>{!options.swappedAxes ? "Left Y Axis" : "X Axis"}</Section.Title>

<Section>
<AxisSettings
Expand All @@ -20,7 +20,7 @@ export default function YAxisSettings({ options, onOptionsChange }) {
/>
</Section>

{options.globalSeriesType !== "heatmap" && (
{options.globalSeriesType !== "heatmap" && !options.swappedAxes && (
<React.Fragment>
<Section.Title>Right Y Axis</Section.Title>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Object {
},
},
"showDataLabels": true,
"swappedAxes": false,
}
`;

Expand All @@ -43,6 +44,13 @@ Object {
}
`;

exports[`Visualizations -> Chart -> Editor -> General Settings Toggles horizontal bar chart 1`] = `
Object {
"seriesOptions": Object {},
"swappedAxes": true,
}
`;

exports[`Visualizations -> Chart -> Editor -> General Settings Toggles legend 1`] = `
Object {
"legend": Object {
Expand Down
4 changes: 2 additions & 2 deletions viz-lib/src/visualizations/chart/Editor/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ export default createTabbedEditor([
},
{
key: "XAxis",
title: "X Axis",
title: ({ swappedAxes }) => (!swappedAxes ? "X Axis" : "Y Axis"),
component: XAxisSettings,
isAvailable: options => !isCustomChart(options) && !isPieChart(options),
},
{
key: "YAxis",
title: "Y Axis",
title: ({ swappedAxes }) => (!swappedAxes ? "Y Axis" : "X Axis"),
component: YAxisSettings,
isAvailable: options => !isCustomChart(options) && !isPieChart(options),
},
Expand Down
6 changes: 3 additions & 3 deletions viz-lib/src/visualizations/chart/Renderer/initChart.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { isArray, isObject, isString, isFunction, startsWith, reduce, merge, map, each } from "lodash";
import resizeObserver from "@/services/resizeObserver";
import { Plotly, prepareData, prepareLayout, updateData, updateYRanges, updateChartSize } from "../plotly";
import { Plotly, prepareData, prepareLayout, updateData, updateAxes, updateChartSize } from "../plotly";

function createErrorHandler(errorHandler) {
return error => {
Expand Down Expand Up @@ -86,7 +86,7 @@ export default function initChart(container, options, data, additionalOptions, o
.then(
createSafeFunction(() =>
updater
.append(updateYRanges(container, plotlyLayout, options))
.append(updateAxes(container, plotlyData, plotlyLayout, options))
.append(updateChartSize(container, plotlyLayout, options))
.process(container)
)
Expand All @@ -100,7 +100,7 @@ export default function initChart(container, options, data, additionalOptions, o
// We need to catch only changes of traces visibility to update stacking
if (isArray(updates) && isObject(updates[0]) && updates[0].visible) {
updateData(plotlyData, options);
updater.append(updateYRanges(container, plotlyLayout, options)).process(container);
updater.append(updateAxes(container, plotlyData, plotlyLayout, options)).process(container);
}
})
);
Expand Down
4 changes: 2 additions & 2 deletions viz-lib/src/visualizations/chart/plotly/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import heatmap from "plotly.js/lib/heatmap";
import prepareData from "./prepareData";
import prepareLayout from "./prepareLayout";
import updateData from "./updateData";
import updateYRanges from "./updateYRanges";
import updateAxes from "./updateAxes";
import updateChartSize from "./updateChartSize";
import { prepareCustomChartData, createCustomChartRenderer } from "./customChartUtils";

Expand All @@ -22,7 +22,7 @@ export {
prepareData,
prepareLayout,
updateData,
updateYRanges,
updateAxes,
updateChartSize,
prepareCustomChartData,
createCustomChartRenderer,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isObject, isNumber } from "lodash";
import { isObject, isNumber, each } from "lodash";

function calculateAxisRange(range, min, max) {
return [isNumber(min) ? min : range[0], isNumber(max) ? max : range[1]];
Expand Down Expand Up @@ -40,7 +40,7 @@ function alignYAxesAtZero(axisA, axisB) {
}
}

export default function updateYRanges(plotlyElement, layout, options) {
export default function updateAxes(plotlyElement, seriesList, layout, options) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gabrieldutra I've merged the changes and kept the name of updateAxes rather than updateYRanges, since the module now deals with them both. The Align Y Axes at Zero feature seems to have retained it's functionality, as expected, but it'd be great if you could check on that.

const updates = {};
if (isObject(layout.yaxis)) {
updates.yaxis = {
Expand All @@ -60,6 +60,7 @@ export default function updateYRanges(plotlyElement, layout, options) {
return [
updates,
() => {
// Update Y Ranges
if (isObject(layout.yaxis)) {
const axisOptions = options.yAxis[0];
const defaultRange = plotlyElement.layout.yaxis.range;
Expand All @@ -74,6 +75,28 @@ export default function updateYRanges(plotlyElement, layout, options) {
updates.yaxis2.range = calculateAxisRange(defaultRange, axisOptions.rangeMin, axisOptions.rangeMax);
}

// Swap Axes
if (options.swappedAxes) {
each(seriesList, series => {
series.orientation = "h";
const { x, y } = series;
series.x = y;
series.y = x;
});

const { xaxis } = layout;
const { yaxis, yaxis2 } = updates;

if (isObject(xaxis) && isObject(yaxis)) {
updates.xaxis = yaxis;
updates.yaxis = xaxis;
}
if (isObject(yaxis2)) {
updates.yaxis2 = null;
}
}

// Align Y axes
if (options.alignYAxesAtZero && isObject(layout.yaxis) && isObject(layout.yaxis2)) {
alignYAxesAtZero(updates.yaxis, updates.yaxis2);
}
Expand Down