diff --git a/packages/perspective-viewer-d3fc/src/js/axis/axisFactory.js b/packages/perspective-viewer-d3fc/src/js/axis/axisFactory.js index a70708e94e..3a66de7f31 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/axisFactory.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/axisFactory.js @@ -103,6 +103,7 @@ export const axisFactory = settings => { settingName = args[0]; return _factory; }; + _factory.settingValue = (...args) => { if (!args.length) { return settingValue; @@ -118,6 +119,7 @@ export const axisFactory = settings => { valueNames = [args[0]]; return _factory; }; + _factory.valueNames = (...args) => { if (!args.length) { return valueNames; diff --git a/packages/perspective-viewer-d3fc/src/js/charts/charts.js b/packages/perspective-viewer-d3fc/src/js/charts/charts.js index a8b1da6b09..8e5a94e8d9 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/charts.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/charts.js @@ -10,6 +10,7 @@ import barChart from "./bar"; import columnChart from "./column"; import lineChart from "./line"; +import xyLine from "./xy-line"; import areaChart from "./area"; import yScatter from "./y-scatter"; import xyScatter from "./xy-scatter"; @@ -19,6 +20,6 @@ import candlestick from "./candlestick"; import sunburst from "./sunburst"; import treemap from "./treemap"; -const chartClasses = [barChart, columnChart, lineChart, areaChart, yScatter, xyScatter, ohlc, candlestick, treemap, sunburst, heatmap]; +const chartClasses = [barChart, columnChart, lineChart, xyLine, areaChart, yScatter, xyScatter, ohlc, candlestick, treemap, sunburst, heatmap]; export default chartClasses; diff --git a/packages/perspective-viewer-d3fc/src/js/charts/xy-line.js b/packages/perspective-viewer-d3fc/src/js/charts/xy-line.js new file mode 100644 index 0000000000..a731aa4263 --- /dev/null +++ b/packages/perspective-viewer-d3fc/src/js/charts/xy-line.js @@ -0,0 +1,107 @@ +/****************************************************************************** + * + * Copyright (c) 2017, the Perspective Authors. + * + * This file is part of the Perspective library, distributed under the terms of + * the Apache License 2.0. The full license can be found in the LICENSE file. + * + */ + +import * as fc from "d3fc"; +import {transposeData} from "../data/transposeData"; +import {axisFactory} from "../axis/axisFactory"; +import {chartSvgFactory} from "../axis/chartFactory"; +import {symbolTypeFromGroups} from "../series/pointSeriesCanvas"; +import {lineSeries} from "../series/lineSeries"; +import {xySplitData} from "../data/xySplitData"; +import {seriesColorsFromGroups} from "../series/seriesColors"; +import {colorGroupLegend} from "../legend/legend"; +import {filterDataByGroup} from "../legend/filter"; +import withGridLines from "../gridlines/gridlines"; +import {hardLimitZeroPadding} from "../d3fc/padding/hardLimitZero"; +import zoomableChart from "../zoom/zoomableChart"; +import nearbyTip from "../tooltip/nearbyTip"; + +function xyLine(container, settings) { + const data = transposeData(xySplitData(settings, filterDataByGroup(settings))); + + const color = seriesColorsFromGroups(settings); + const symbols = symbolTypeFromGroups(settings); + + let legend = null; + if (color.domain().length > 2) { + legend = colorGroupLegend() + .settings(settings) + .scale(symbols) + .color(color); + } + + const series = fc + .seriesSvgRepeat() + .series(lineSeries(settings, color)) + .orient("horizontal"); + + const paddingStrategy = hardLimitZeroPadding() + .pad([0.1, 0.1]) + .padUnit("percent"); + + const xAxisFactory = axisFactory(settings) + .settingName("mainValues") + .settingValue(settings.mainValues[0].name) + .valueName("crossValue") + .paddingStrategy(paddingStrategy); + + const yAxisFactory = axisFactory(settings) + .settingName("mainValues") + .settingValue(settings.mainValues[1].name) + .valueName("mainValue") + .orient("vertical") + .paddingStrategy(paddingStrategy); + + const yAxis = yAxisFactory(data); + const xAxis = xAxisFactory(data); + + const plotSeries = withGridLines(series, settings).orient("vertical"); + + const chart = chartSvgFactory(xAxis, yAxis) + .xLabel(settings.mainValues[0].name) + .yLabel(settings.mainValues[1].name) + .plotArea(plotSeries); + + chart.xNice && chart.xNice(); + chart.yNice && chart.yNice(); + + const zoomChart = zoomableChart() + .chart(chart) + .settings(settings) + .xScale(xAxis.scale) + .yScale(yAxis.scale); + + const toolTip = nearbyTip() + .settings(settings) + .xScale(xAxis.scale) + .yScale(yAxis.scale) + .color(color) + .data(data); + + container.datum(data).call(zoomChart); + container.call(toolTip); + if (legend) { + container.call(legend); + } +} + +xyLine.plugin = { + type: "d3_xy_line", + name: "X/Y Line Chart", + max_cells: 50000, + max_columns: 50, + initial: { + type: "number", + count: 2, + names: ["X Axis", "Y Axis"] + }, + selectMode: "toggle" +}; + +export default xyLine; diff --git a/packages/perspective-viewer-d3fc/src/js/charts/xy-scatter.js b/packages/perspective-viewer-d3fc/src/js/charts/xy-scatter.js index 4a22737eb0..2a94b3388f 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/xy-scatter.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/xy-scatter.js @@ -92,6 +92,7 @@ function xyScatter(container, settings) { container.call(toolTip); if (legend) container.call(legend); } + xyScatter.plugin = { type: "d3_xy_scatter", name: "X/Y Scatter Chart", diff --git a/packages/perspective-viewer-d3fc/src/js/data/xySplitData.js b/packages/perspective-viewer-d3fc/src/js/data/xySplitData.js new file mode 100644 index 0000000000..7e2e6998c4 --- /dev/null +++ b/packages/perspective-viewer-d3fc/src/js/data/xySplitData.js @@ -0,0 +1,28 @@ +/****************************************************************************** + * + * Copyright (c) 2017, the Perspective Authors. + * + * This file is part of the Perspective library, distributed under the terms of + * the Apache License 2.0. The full license can be found in the LICENSE file. + * + */ + +import {groupFromKey} from "../series/seriesKey"; + +export function xySplitData(settings, data) { + const n_cols = settings.mainValues.length; + + return data.map(col => { + const cols = Object.keys(col).filter(key => key !== "__ROW_PATH__"); + const row = new Array(cols.length / n_cols); + for (let i = 0; i < cols.length / n_cols; i++) { + row[i] = { + key: groupFromKey(cols[i * n_cols]), + crossValue: col[cols[i * n_cols]], + mainValue: col[cols[i * n_cols + 1]], + row: col + }; + } + return row; + }); +} diff --git a/packages/perspective-viewer-d3fc/src/js/index/xy-line.js b/packages/perspective-viewer-d3fc/src/js/index/xy-line.js new file mode 100644 index 0000000000..b34e39220a --- /dev/null +++ b/packages/perspective-viewer-d3fc/src/js/index/xy-line.js @@ -0,0 +1,11 @@ +/****************************************************************************** + * + * Copyright (c) 2017, the Perspective Authors. + * + * This file is part of the Perspective library, distributed under the terms of + * the Apache License 2.0. The full license can be found in the LICENSE file. + * + */ + +import {register} from "../plugin/plugin.js"; +register("d3_xy_line"); diff --git a/packages/perspective-viewer-d3fc/src/js/legend/legend.js b/packages/perspective-viewer-d3fc/src/js/legend/legend.js index 05a3fe30eb..3fc3c930f6 100644 --- a/packages/perspective-viewer-d3fc/src/js/legend/legend.js +++ b/packages/perspective-viewer-d3fc/src/js/legend/legend.js @@ -22,6 +22,7 @@ const scrollColorLegend = settings => .shapeRadius(6), settings ); + const scrollSymbolLegend = settings => scrollableLegend( d3Legend @@ -33,6 +34,7 @@ const scrollSymbolLegend = settings => export const colorLegend = () => legendComponent(scrollColorLegend); export const symbolLegend = () => legendComponent(scrollSymbolLegend, symbolScale); +export const colorGroupLegend = () => legendComponent(scrollColorLegend, symbolScale); function symbolScale(fromScale) { if (!fromScale) return null; @@ -88,7 +90,7 @@ function legendComponent(scrollLegendComponent, scaleModifier) { if (color) { cells - .select("path") + .select("circle, path") .style("fill", d => (isHidden(d) ? null : color(d))) .style("stroke", d => (isHidden(d) ? null : withoutOpacity(color(d)))); } diff --git a/packages/perspective-viewer-d3fc/src/less/chart.less b/packages/perspective-viewer-d3fc/src/less/chart.less index 2d8afac12f..d1811a98fb 100644 --- a/packages/perspective-viewer-d3fc/src/less/chart.less +++ b/packages/perspective-viewer-d3fc/src/less/chart.less @@ -192,6 +192,7 @@ display: none; } &.d3_xy_scatter, + &.d3_xy_line, &.d3_ohlc, &.d3_candlestick { & d3fc-canvas.plot-area { @@ -243,6 +244,8 @@ &.d3_y_bar .y-axis path, &.d3_y_line .y-axis path, + &.d3_xy_line .y-axis path, + &.d3_xy_line .x-axis path, &.d3_y_area .y-axis path, &.d3_y_scatter .y-axis path, &.d3_xy_scatter .y-axis path, diff --git a/packages/perspective-viewer-d3fc/test/js/integration/xy-line.spec.js b/packages/perspective-viewer-d3fc/test/js/integration/xy-line.spec.js new file mode 100644 index 0000000000..2402ffe3d2 --- /dev/null +++ b/packages/perspective-viewer-d3fc/test/js/integration/xy-line.spec.js @@ -0,0 +1,26 @@ +/****************************************************************************** + * + * Copyright (c) 2017, the Perspective Authors. + * + * This file is part of the Perspective library, distributed under the terms of + * the Apache License 2.0. The full license can be found in the LICENSE file. + * + */ + +const path = require("path"); + +const utils = require("@finos/perspective-test"); +const simple_tests = require("@finos/perspective-viewer/test/js/simple_tests.js"); + +const {withTemplate} = require("./simple-template"); +withTemplate("xyline", "d3_xy_line", {columns: ["Sales", "Quantity"]}); + +utils.with_server({}, () => { + describe.page( + "xyline.html", + () => { + simple_tests.default(); + }, + {reload_page: false, root: path.join(__dirname, "..", "..", "..")} + ); +}); diff --git a/packages/perspective-viewer-d3fc/test/results/linux.docker.json b/packages/perspective-viewer-d3fc/test/results/linux.docker.json index 8fe0246303..3ae36e7b0e 100644 --- a/packages/perspective-viewer-d3fc/test/results/linux.docker.json +++ b/packages/perspective-viewer-d3fc/test/results/linux.docker.json @@ -1,7 +1,7 @@ { "candlestick_filter_by_a_single_instrument_": "f988ca6494d7a36bada09928cd1a544e", "candlestick_filter_to_date_range_": "8ca4da0a6229d4f9db4a845d5d415c20", - "__GIT_COMMIT__": "199af527de833da4f5865d57570aae55f8a18849", + "__GIT_COMMIT__": "715e26e221a61db4053e9e10b3aa6804182a91d6", "ohlc_filter_by_a_single_instrument_": "0110fac1f2befac1b97a9d33f0022acf", "ohlc_filter_to_date_range_": "3ec466996be47e2c8df135a4303bf383", "scatter_shows_a_grid_without_any_settings_applied_": "8677946ab48f16a376c421500d59e6c0", @@ -128,5 +128,18 @@ "treemap_tooltip_columns_works": "9b3a44cc1b4b1d11cb8b635c850a0612", "line_Sets_a_category_axis_when_pivoted_by_a_computed_datetime": "eb1c86dc44988ad9a65fdd5a335850b8", "sunburst_sunburst_label_shows_formatted_date": "590f474e076fd49ce10eb5e97bfc66d3", - "treemap_treemap_label_shows_formatted_date": "6c39c766e14c64c28316137f5f1ff85b" + "treemap_treemap_label_shows_formatted_date": "6c39c766e14c64c28316137f5f1ff85b", + "xyline_shows_a_grid_without_any_settings_applied_": "6d4bdd941a04d6e39fe14c2ea001886e", + "xyline_pivots_by_a_row_": "14c3bc345abb892ccab5ac2dad1f777a", + "xyline_pivots_by_two_rows_": "5ea6481c8e465b87a1fcaca2e8b2a759", + "xyline_pivots_by_a_column_": "665a82227ede8b9129c6c88574097eae", + "xyline_pivots_by_a_row_and_a_column_": "29f56130b2d178def3a3feb24304d779", + "xyline_pivots_by_two_rows_and_two_columns_": "70fc58b149bb28dd571fce92616eec02", + "xyline_sorts_by_a_hidden_column_": "c38fc638ae076df1f0caf51214bf7938", + "xyline_sorts_by_a_numeric_column_": "eb6022557a889eafce59eda986600f0c", + "xyline_filters_by_a_numeric_column_": "076a949ff4297a506463a0182159fc8b", + "xyline_filters_by_a_datetime_column_": "e43eb7c8d45ff150e8c4c8713b3bafd5", + "xyline_highlights_invalid_filter_": "f8f312392516ed4dfa070d436f6ede30", + "xyline_sorts_by_an_alpha_column_": "9cc9fc4d49b0671eeca80ad272664256", + "xyline_displays_visible_columns_": "533a7f651187eb77e32b2e0690b0b7b9" } \ No newline at end of file