├── tests ├── config.nims ├── tIssue100.nim ├── test_issue5.nim ├── tvega.nim ├── test_filter.nim ├── test_issue2.nim ├── testVega.nim ├── tCompareRecipes.nim └── testsFormula.nim ├── media ├── scatter.png ├── freqpoly.png ├── newton_eq.png ├── bar_example.png ├── scatterColor.png ├── simpleHisto.png ├── classVsNormCty.png ├── scatterFromDF.png ├── expected │ ├── rJoyplot.png │ ├── rColormaps.png │ ├── rCustomFill.png │ ├── rErrorBar.png │ ├── rFacetTpa.png │ ├── rGeomSmooth.png │ ├── rLinearFit.png │ ├── rScaleXDate.png │ ├── rSimpleTile.png │ ├── rTikZLandau.png │ ├── rCustomBreaks.png │ ├── rFacetRaster.png │ ├── rLimitXRange.png │ ├── rLinePlotSize.png │ ├── rSimpleFacet.png │ ├── rSimpleRaster.png │ ├── rCustomColormap.png │ ├── rCustomMargins.png │ ├── rDiscreteXLine.png │ ├── rDiscreteYAxis.png │ ├── rEnlargeXRange.png │ ├── rFormatDatesPlot.png │ ├── rHighlightMinMax.png │ ├── rMpgHistoNumBins.png │ ├── rMultiSubplots.png │ ├── rMultipleLegends.png │ ├── rNegativeBarPlot.png │ ├── rPeriodicTable.png │ ├── rPointInPolygons.png │ ├── rPrebinnedHisto.png │ ├── rRidgeLineGauss.png │ ├── rSimpleGeomText.png │ ├── rSimpleLinePlot.png │ ├── rSimpleVegaLite.png │ ├── rAnnotateMaxValues.png │ ├── rAnnotatedHeatmap.png │ ├── rAxionMassesLogLog.png │ ├── rBarPlotCompStats.png │ ├── rBothDiscreteAxes.png │ ├── rCustomAnnotations.png │ ├── rFormulaAesthetic.png │ ├── rFreqPolyWithAlpha.png │ ├── rHistogramDensity.png │ ├── rHistogramOutline.png │ ├── rMpgDiscreteXScale.png │ ├── rMpgHistoBinWidth.png │ ├── rMpgSimpleBarPlot.png │ ├── rMpgStackedBarPlot.png │ ├── rWeightedHistogram.png │ ├── rAxionMassVsDensity.png │ ├── rBarPlotRotatedLabels.png │ ├── rClassifiedGeomText.png │ ├── rCreateMarginBuffer.png │ ├── rFormatDecimalsPlot.png │ ├── rLongTitleMultiline.png │ ├── rMpgCustomColorPoint.png │ ├── rMpgHistoCustomBreaks.png │ ├── rMpgHistoPlusPoints.png │ ├── rMpgStackedPointPlot.png │ ├── rNewtonAcceleration.png │ ├── rRidgeLineGaussBlack.png │ ├── rStackedMpgFreqpoly.png │ ├── rStackedMpgHistogram.png │ ├── rTwoSensorsBadStyle.png │ ├── rTwoSensorsGoodStyle.png │ ├── rAnnotateUsingGeomText.png │ ├── rAutoColoredNeuralSpikes.png │ ├── rCustomColoredNeuralSpikes.png │ ├── rMassAttenuationFunction.png │ └── rMpgContinuousColorPoints.png ├── histoPlusFreqpoly.png ├── recipes │ ├── rErrorBar.png │ ├── rFacetTpa.png │ ├── rJoyplot.png │ ├── rColormaps.png │ ├── rCustomFill.png │ ├── rFacetRaster.png │ ├── rGeomSmooth.png │ ├── rLimitXRange.png │ ├── rLinearFit.png │ ├── rScaleXDate.png │ ├── rSimpleFacet.png │ ├── rSimpleTile.png │ ├── rTikZLandau.png │ ├── rCustomBreaks.png │ ├── rCustomMargins.png │ ├── rDiscreteXLine.png │ ├── rDiscreteYAxis.png │ ├── rEnlargeXRange.png │ ├── rLinePlotSize.png │ ├── rMultiSubplots.png │ ├── rPeriodicTable.png │ ├── rSimpleRaster.png │ ├── rAnnotatedHeatmap.png │ ├── rBarPlotCompStats.png │ ├── rBothDiscreteAxes.png │ ├── rCustomColormap.png │ ├── rFormatDatesPlot.png │ ├── rFormulaAesthetic.png │ ├── rHighlightMinMax.png │ ├── rHistogramDensity.png │ ├── rHistogramOutline.png │ ├── rMpgHistoBinWidth.png │ ├── rMpgHistoNumBins.png │ ├── rMpgSimpleBarPlot.png │ ├── rMultipleLegends.png │ ├── rNegativeBarPlot.png │ ├── rPointInPolygons.png │ ├── rPrebinnedHisto.png │ ├── rRidgeLineGauss.png │ ├── rSimpleGeomText.png │ ├── rSimpleLinePlot.png │ ├── rSimpleVegaLite.png │ ├── rAnnotateMaxValues.png │ ├── rAxionMassVsDensity.png │ ├── rAxionMassesLogLog.png │ ├── rClassifiedGeomText.png │ ├── rCreateMarginBuffer.png │ ├── rCustomAnnotations.png │ ├── rFormatDecimalsPlot.png │ ├── rFreqPolyWithAlpha.png │ ├── rLongTitleMultiline.png │ ├── rMpgDiscreteXScale.png │ ├── rMpgHistoPlusPoints.png │ ├── rMpgStackedBarPlot.png │ ├── rNewtonAcceleration.png │ ├── rStackedMpgFreqpoly.png │ ├── rTwoSensorsBadStyle.png │ ├── rWeightedHistogram.png │ ├── rAnnotateUsingGeomText.png │ ├── rBarPlotRotatedLabels.png │ ├── rMpgCustomColorPoint.png │ ├── rMpgHistoCustomBreaks.png │ ├── rMpgStackedPointPlot.png │ ├── rRidgeLineGaussBlack.png │ ├── rStackedMpgHistogram.png │ ├── rTwoSensorsGoodStyle.png │ ├── rAutoColoredNeuralSpikes.png │ ├── rMassAttenuationFunction.png │ ├── rCustomColoredNeuralSpikes.png │ └── rMpgContinuousColorPoints.png ├── event_run_305_num_59.png ├── vega_backend_example.png ├── facet_wrap_manufacturer.png └── issues │ ├── read_csv_ref_types.png │ ├── both_case_callstack.png │ ├── ref_types_incl_strip.png │ ├── callgrind_cycles_fast.png │ ├── callgrind_cycles_slow.png │ ├── kcachegrind_weirdSlowFast.png │ └── non_ref_types_incl_strip.png ├── recipes ├── rDiscreteYAxis.nim ├── rMpgSimpleBarPlot.nim ├── rMpgHistoBinWidth.nim ├── rMpgStackedBarPlot.nim ├── rLimitXRange.nim ├── rBothDiscreteAxes.nim ├── rEnlargeXRange.nim ├── rHistogramDensity.nim ├── rStackedMpgFreqpoly.nim ├── rStackedMpgHistogram.nim ├── rFreqPolyWithAlpha.nim ├── rSimpleGeomText.nim ├── rMultipleLegends.nim ├── rMpgContinuousColorPoints.nim ├── rMpgHistoNumBins.nim ├── allRecipes.nim ├── rFormulaAesthetic.nim ├── rMpgHistoCustomBreaks.nim ├── rCreateMarginBuffer.nim ├── rLinearFit.nim ├── rSimpleFacet.nim ├── rClassifiedGeomText.nim ├── rMpgStackedPointPlot.nim ├── rMpgCustomColorPoint.nim ├── rWeightedHistogram.nim ├── rMpgDiscreteXScale.nim ├── allRecipesJson.nim ├── rTwoSensorsBadStyle.nim ├── rMpgHistoPlusPoints.nim ├── rSimpleVegaLite.nim ├── rHistogramOutline.nim ├── rPrebinnedHisto.nim ├── rSimpleLinePlot.nim ├── rLinePlotSize.nim ├── rTwoSensorsGoodStyle.nim ├── rBarPlotRotatedLabels.nim ├── rNegativeBarPlot.nim ├── rAnnotateUsingGeomText.nim ├── rJoyplot.nim ├── rLongTitleMultiline.nim ├── rFormatDecimalsPlot.nim ├── rSimpleRaster.nim ├── rCustomBreaks.nim ├── rSimpleTile.nim ├── rRidgeLineGaussBlack.nim ├── rCustomMargins.nim ├── rErrorBar.nim ├── rAnnotatedHeatmap.nim ├── rFacetRaster.nim ├── rGeomSmooth.nim ├── rScaleXDate.nim ├── rFacetTpa.nim ├── rCustomFill.nim ├── rAnnotateMaxValues.nim ├── rFormatDatesPlot.nim ├── rBarPlotCompStats.nim ├── rHighlightMinMax.nim ├── rCustomColormap.nim ├── rAutoColoredNeuralSpikes.nim ├── rColormaps.nim ├── rMultiSubplots.nim ├── rNewtonAcceleration.nim ├── rRidgeLineGauss.nim ├── rDiscreteXLine.nim ├── rCustomColoredNeuralSpikes.nim ├── rCustomAnnotations.nim ├── rMassAttenuationFunction.nim ├── rTikZLandau.nim ├── runRecipes.nim ├── rPointInPolygons.nim ├── rAxionMassesLogLog.nim ├── rPeriodicTable.nim ├── recipeFiles.nim └── rAxionMassVsDensity.nim ├── .gitignore ├── playground └── randomPlots.nim ├── nim.cfg ├── benchmarks ├── million_scatter │ ├── million_ggplot2.R │ ├── million_matplotlib_scatter.py │ ├── million_seaborn.py │ ├── million_matplotlib.py │ └── million_ggplotnim.nim └── bench_many_geoms.nim ├── bookPlots ├── readme.org ├── weirdFastSlow.nim └── chapter2.nim ├── data ├── presidential.csv ├── mass_attenuation_nist_data.txt └── msleep.csv ├── src └── ggplotnim │ ├── ggplot_vegatex.nim │ ├── ggplot_utils.nim │ ├── theme_toml.nim │ ├── ggplot_theme.nim │ ├── ggplot_styles.nim │ └── ggplot_scales.nim ├── .travis.yml ├── LICENSE ├── .github └── workflows │ └── ci.yml ├── ggplotnim.nimble ├── docs ├── docs.nim ├── vega_utils.html └── ggplot_utils.html └── README.org /tests/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../src") -------------------------------------------------------------------------------- /media/scatter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/scatter.png -------------------------------------------------------------------------------- /media/freqpoly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/freqpoly.png -------------------------------------------------------------------------------- /media/newton_eq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/newton_eq.png -------------------------------------------------------------------------------- /media/bar_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/bar_example.png -------------------------------------------------------------------------------- /media/scatterColor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/scatterColor.png -------------------------------------------------------------------------------- /media/simpleHisto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/simpleHisto.png -------------------------------------------------------------------------------- /media/classVsNormCty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/classVsNormCty.png -------------------------------------------------------------------------------- /media/scatterFromDF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/scatterFromDF.png -------------------------------------------------------------------------------- /media/expected/rJoyplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rJoyplot.png -------------------------------------------------------------------------------- /media/histoPlusFreqpoly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/histoPlusFreqpoly.png -------------------------------------------------------------------------------- /media/recipes/rErrorBar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rErrorBar.png -------------------------------------------------------------------------------- /media/recipes/rFacetTpa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rFacetTpa.png -------------------------------------------------------------------------------- /media/recipes/rJoyplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rJoyplot.png -------------------------------------------------------------------------------- /media/event_run_305_num_59.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/event_run_305_num_59.png -------------------------------------------------------------------------------- /media/expected/rColormaps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rColormaps.png -------------------------------------------------------------------------------- /media/expected/rCustomFill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rCustomFill.png -------------------------------------------------------------------------------- /media/expected/rErrorBar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rErrorBar.png -------------------------------------------------------------------------------- /media/expected/rFacetTpa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rFacetTpa.png -------------------------------------------------------------------------------- /media/expected/rGeomSmooth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rGeomSmooth.png -------------------------------------------------------------------------------- /media/expected/rLinearFit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rLinearFit.png -------------------------------------------------------------------------------- /media/expected/rScaleXDate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rScaleXDate.png -------------------------------------------------------------------------------- /media/expected/rSimpleTile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rSimpleTile.png -------------------------------------------------------------------------------- /media/expected/rTikZLandau.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rTikZLandau.png -------------------------------------------------------------------------------- /media/recipes/rColormaps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rColormaps.png -------------------------------------------------------------------------------- /media/recipes/rCustomFill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rCustomFill.png -------------------------------------------------------------------------------- /media/recipes/rFacetRaster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rFacetRaster.png -------------------------------------------------------------------------------- /media/recipes/rGeomSmooth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rGeomSmooth.png -------------------------------------------------------------------------------- /media/recipes/rLimitXRange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rLimitXRange.png -------------------------------------------------------------------------------- /media/recipes/rLinearFit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rLinearFit.png -------------------------------------------------------------------------------- /media/recipes/rScaleXDate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rScaleXDate.png -------------------------------------------------------------------------------- /media/recipes/rSimpleFacet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rSimpleFacet.png -------------------------------------------------------------------------------- /media/recipes/rSimpleTile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rSimpleTile.png -------------------------------------------------------------------------------- /media/recipes/rTikZLandau.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rTikZLandau.png -------------------------------------------------------------------------------- /media/vega_backend_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/vega_backend_example.png -------------------------------------------------------------------------------- /media/expected/rCustomBreaks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rCustomBreaks.png -------------------------------------------------------------------------------- /media/expected/rFacetRaster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rFacetRaster.png -------------------------------------------------------------------------------- /media/expected/rLimitXRange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rLimitXRange.png -------------------------------------------------------------------------------- /media/expected/rLinePlotSize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rLinePlotSize.png -------------------------------------------------------------------------------- /media/expected/rSimpleFacet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rSimpleFacet.png -------------------------------------------------------------------------------- /media/expected/rSimpleRaster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rSimpleRaster.png -------------------------------------------------------------------------------- /media/recipes/rCustomBreaks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rCustomBreaks.png -------------------------------------------------------------------------------- /media/recipes/rCustomMargins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rCustomMargins.png -------------------------------------------------------------------------------- /media/recipes/rDiscreteXLine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rDiscreteXLine.png -------------------------------------------------------------------------------- /media/recipes/rDiscreteYAxis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rDiscreteYAxis.png -------------------------------------------------------------------------------- /media/recipes/rEnlargeXRange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rEnlargeXRange.png -------------------------------------------------------------------------------- /media/recipes/rLinePlotSize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rLinePlotSize.png -------------------------------------------------------------------------------- /media/recipes/rMultiSubplots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rMultiSubplots.png -------------------------------------------------------------------------------- /media/recipes/rPeriodicTable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rPeriodicTable.png -------------------------------------------------------------------------------- /media/recipes/rSimpleRaster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rSimpleRaster.png -------------------------------------------------------------------------------- /media/expected/rCustomColormap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rCustomColormap.png -------------------------------------------------------------------------------- /media/expected/rCustomMargins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rCustomMargins.png -------------------------------------------------------------------------------- /media/expected/rDiscreteXLine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rDiscreteXLine.png -------------------------------------------------------------------------------- /media/expected/rDiscreteYAxis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rDiscreteYAxis.png -------------------------------------------------------------------------------- /media/expected/rEnlargeXRange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rEnlargeXRange.png -------------------------------------------------------------------------------- /media/expected/rFormatDatesPlot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rFormatDatesPlot.png -------------------------------------------------------------------------------- /media/expected/rHighlightMinMax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rHighlightMinMax.png -------------------------------------------------------------------------------- /media/expected/rMpgHistoNumBins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rMpgHistoNumBins.png -------------------------------------------------------------------------------- /media/expected/rMultiSubplots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rMultiSubplots.png -------------------------------------------------------------------------------- /media/expected/rMultipleLegends.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rMultipleLegends.png -------------------------------------------------------------------------------- /media/expected/rNegativeBarPlot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rNegativeBarPlot.png -------------------------------------------------------------------------------- /media/expected/rPeriodicTable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rPeriodicTable.png -------------------------------------------------------------------------------- /media/expected/rPointInPolygons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rPointInPolygons.png -------------------------------------------------------------------------------- /media/expected/rPrebinnedHisto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rPrebinnedHisto.png -------------------------------------------------------------------------------- /media/expected/rRidgeLineGauss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rRidgeLineGauss.png -------------------------------------------------------------------------------- /media/expected/rSimpleGeomText.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rSimpleGeomText.png -------------------------------------------------------------------------------- /media/expected/rSimpleLinePlot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rSimpleLinePlot.png -------------------------------------------------------------------------------- /media/expected/rSimpleVegaLite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rSimpleVegaLite.png -------------------------------------------------------------------------------- /media/facet_wrap_manufacturer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/facet_wrap_manufacturer.png -------------------------------------------------------------------------------- /media/issues/read_csv_ref_types.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/issues/read_csv_ref_types.png -------------------------------------------------------------------------------- /media/recipes/rAnnotatedHeatmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rAnnotatedHeatmap.png -------------------------------------------------------------------------------- /media/recipes/rBarPlotCompStats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rBarPlotCompStats.png -------------------------------------------------------------------------------- /media/recipes/rBothDiscreteAxes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rBothDiscreteAxes.png -------------------------------------------------------------------------------- /media/recipes/rCustomColormap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rCustomColormap.png -------------------------------------------------------------------------------- /media/recipes/rFormatDatesPlot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rFormatDatesPlot.png -------------------------------------------------------------------------------- /media/recipes/rFormulaAesthetic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rFormulaAesthetic.png -------------------------------------------------------------------------------- /media/recipes/rHighlightMinMax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rHighlightMinMax.png -------------------------------------------------------------------------------- /media/recipes/rHistogramDensity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rHistogramDensity.png -------------------------------------------------------------------------------- /media/recipes/rHistogramOutline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rHistogramOutline.png -------------------------------------------------------------------------------- /media/recipes/rMpgHistoBinWidth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rMpgHistoBinWidth.png -------------------------------------------------------------------------------- /media/recipes/rMpgHistoNumBins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rMpgHistoNumBins.png -------------------------------------------------------------------------------- /media/recipes/rMpgSimpleBarPlot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rMpgSimpleBarPlot.png -------------------------------------------------------------------------------- /media/recipes/rMultipleLegends.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rMultipleLegends.png -------------------------------------------------------------------------------- /media/recipes/rNegativeBarPlot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rNegativeBarPlot.png -------------------------------------------------------------------------------- /media/recipes/rPointInPolygons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rPointInPolygons.png -------------------------------------------------------------------------------- /media/recipes/rPrebinnedHisto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rPrebinnedHisto.png -------------------------------------------------------------------------------- /media/recipes/rRidgeLineGauss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rRidgeLineGauss.png -------------------------------------------------------------------------------- /media/recipes/rSimpleGeomText.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rSimpleGeomText.png -------------------------------------------------------------------------------- /media/recipes/rSimpleLinePlot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rSimpleLinePlot.png -------------------------------------------------------------------------------- /media/recipes/rSimpleVegaLite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rSimpleVegaLite.png -------------------------------------------------------------------------------- /media/expected/rAnnotateMaxValues.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rAnnotateMaxValues.png -------------------------------------------------------------------------------- /media/expected/rAnnotatedHeatmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rAnnotatedHeatmap.png -------------------------------------------------------------------------------- /media/expected/rAxionMassesLogLog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rAxionMassesLogLog.png -------------------------------------------------------------------------------- /media/expected/rBarPlotCompStats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rBarPlotCompStats.png -------------------------------------------------------------------------------- /media/expected/rBothDiscreteAxes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rBothDiscreteAxes.png -------------------------------------------------------------------------------- /media/expected/rCustomAnnotations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rCustomAnnotations.png -------------------------------------------------------------------------------- /media/expected/rFormulaAesthetic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rFormulaAesthetic.png -------------------------------------------------------------------------------- /media/expected/rFreqPolyWithAlpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rFreqPolyWithAlpha.png -------------------------------------------------------------------------------- /media/expected/rHistogramDensity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rHistogramDensity.png -------------------------------------------------------------------------------- /media/expected/rHistogramOutline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rHistogramOutline.png -------------------------------------------------------------------------------- /media/expected/rMpgDiscreteXScale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rMpgDiscreteXScale.png -------------------------------------------------------------------------------- /media/expected/rMpgHistoBinWidth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rMpgHistoBinWidth.png -------------------------------------------------------------------------------- /media/expected/rMpgSimpleBarPlot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rMpgSimpleBarPlot.png -------------------------------------------------------------------------------- /media/expected/rMpgStackedBarPlot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rMpgStackedBarPlot.png -------------------------------------------------------------------------------- /media/expected/rWeightedHistogram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rWeightedHistogram.png -------------------------------------------------------------------------------- /media/issues/both_case_callstack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/issues/both_case_callstack.png -------------------------------------------------------------------------------- /media/issues/ref_types_incl_strip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/issues/ref_types_incl_strip.png -------------------------------------------------------------------------------- /media/recipes/rAnnotateMaxValues.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rAnnotateMaxValues.png -------------------------------------------------------------------------------- /media/recipes/rAxionMassVsDensity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rAxionMassVsDensity.png -------------------------------------------------------------------------------- /media/recipes/rAxionMassesLogLog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rAxionMassesLogLog.png -------------------------------------------------------------------------------- /media/recipes/rClassifiedGeomText.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rClassifiedGeomText.png -------------------------------------------------------------------------------- /media/recipes/rCreateMarginBuffer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rCreateMarginBuffer.png -------------------------------------------------------------------------------- /media/recipes/rCustomAnnotations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rCustomAnnotations.png -------------------------------------------------------------------------------- /media/recipes/rFormatDecimalsPlot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rFormatDecimalsPlot.png -------------------------------------------------------------------------------- /media/recipes/rFreqPolyWithAlpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rFreqPolyWithAlpha.png -------------------------------------------------------------------------------- /media/recipes/rLongTitleMultiline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rLongTitleMultiline.png -------------------------------------------------------------------------------- /media/recipes/rMpgDiscreteXScale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rMpgDiscreteXScale.png -------------------------------------------------------------------------------- /media/recipes/rMpgHistoPlusPoints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rMpgHistoPlusPoints.png -------------------------------------------------------------------------------- /media/recipes/rMpgStackedBarPlot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rMpgStackedBarPlot.png -------------------------------------------------------------------------------- /media/recipes/rNewtonAcceleration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rNewtonAcceleration.png -------------------------------------------------------------------------------- /media/recipes/rStackedMpgFreqpoly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rStackedMpgFreqpoly.png -------------------------------------------------------------------------------- /media/recipes/rTwoSensorsBadStyle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rTwoSensorsBadStyle.png -------------------------------------------------------------------------------- /media/recipes/rWeightedHistogram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rWeightedHistogram.png -------------------------------------------------------------------------------- /media/expected/rAxionMassVsDensity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rAxionMassVsDensity.png -------------------------------------------------------------------------------- /media/expected/rBarPlotRotatedLabels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rBarPlotRotatedLabels.png -------------------------------------------------------------------------------- /media/expected/rClassifiedGeomText.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rClassifiedGeomText.png -------------------------------------------------------------------------------- /media/expected/rCreateMarginBuffer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rCreateMarginBuffer.png -------------------------------------------------------------------------------- /media/expected/rFormatDecimalsPlot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rFormatDecimalsPlot.png -------------------------------------------------------------------------------- /media/expected/rLongTitleMultiline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rLongTitleMultiline.png -------------------------------------------------------------------------------- /media/expected/rMpgCustomColorPoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rMpgCustomColorPoint.png -------------------------------------------------------------------------------- /media/expected/rMpgHistoCustomBreaks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rMpgHistoCustomBreaks.png -------------------------------------------------------------------------------- /media/expected/rMpgHistoPlusPoints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rMpgHistoPlusPoints.png -------------------------------------------------------------------------------- /media/expected/rMpgStackedPointPlot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rMpgStackedPointPlot.png -------------------------------------------------------------------------------- /media/expected/rNewtonAcceleration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rNewtonAcceleration.png -------------------------------------------------------------------------------- /media/expected/rRidgeLineGaussBlack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rRidgeLineGaussBlack.png -------------------------------------------------------------------------------- /media/expected/rStackedMpgFreqpoly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rStackedMpgFreqpoly.png -------------------------------------------------------------------------------- /media/expected/rStackedMpgHistogram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rStackedMpgHistogram.png -------------------------------------------------------------------------------- /media/expected/rTwoSensorsBadStyle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rTwoSensorsBadStyle.png -------------------------------------------------------------------------------- /media/expected/rTwoSensorsGoodStyle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rTwoSensorsGoodStyle.png -------------------------------------------------------------------------------- /media/issues/callgrind_cycles_fast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/issues/callgrind_cycles_fast.png -------------------------------------------------------------------------------- /media/issues/callgrind_cycles_slow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/issues/callgrind_cycles_slow.png -------------------------------------------------------------------------------- /media/recipes/rAnnotateUsingGeomText.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rAnnotateUsingGeomText.png -------------------------------------------------------------------------------- /media/recipes/rBarPlotRotatedLabels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rBarPlotRotatedLabels.png -------------------------------------------------------------------------------- /media/recipes/rMpgCustomColorPoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rMpgCustomColorPoint.png -------------------------------------------------------------------------------- /media/recipes/rMpgHistoCustomBreaks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rMpgHistoCustomBreaks.png -------------------------------------------------------------------------------- /media/recipes/rMpgStackedPointPlot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rMpgStackedPointPlot.png -------------------------------------------------------------------------------- /media/recipes/rRidgeLineGaussBlack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rRidgeLineGaussBlack.png -------------------------------------------------------------------------------- /media/recipes/rStackedMpgHistogram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rStackedMpgHistogram.png -------------------------------------------------------------------------------- /media/recipes/rTwoSensorsGoodStyle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rTwoSensorsGoodStyle.png -------------------------------------------------------------------------------- /media/expected/rAnnotateUsingGeomText.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rAnnotateUsingGeomText.png -------------------------------------------------------------------------------- /media/issues/kcachegrind_weirdSlowFast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/issues/kcachegrind_weirdSlowFast.png -------------------------------------------------------------------------------- /media/issues/non_ref_types_incl_strip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/issues/non_ref_types_incl_strip.png -------------------------------------------------------------------------------- /media/recipes/rAutoColoredNeuralSpikes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rAutoColoredNeuralSpikes.png -------------------------------------------------------------------------------- /media/recipes/rMassAttenuationFunction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rMassAttenuationFunction.png -------------------------------------------------------------------------------- /media/expected/rAutoColoredNeuralSpikes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rAutoColoredNeuralSpikes.png -------------------------------------------------------------------------------- /media/expected/rCustomColoredNeuralSpikes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rCustomColoredNeuralSpikes.png -------------------------------------------------------------------------------- /media/expected/rMassAttenuationFunction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rMassAttenuationFunction.png -------------------------------------------------------------------------------- /media/expected/rMpgContinuousColorPoints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/expected/rMpgContinuousColorPoints.png -------------------------------------------------------------------------------- /media/recipes/rCustomColoredNeuralSpikes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rCustomColoredNeuralSpikes.png -------------------------------------------------------------------------------- /media/recipes/rMpgContinuousColorPoints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vindaar/ggplotnim/HEAD/media/recipes/rMpgContinuousColorPoints.png -------------------------------------------------------------------------------- /recipes/rDiscreteYAxis.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | ggplot(df, aes("hwy", "cyl")) + 4 | geom_point() + 5 | ggsave("media/recipes/rDiscreteYAxis.png") 6 | -------------------------------------------------------------------------------- /recipes/rMpgSimpleBarPlot.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | ggplot(df, aes("class")) + 4 | geom_bar() + 5 | ggsave("media/recipes/rMpgSimpleBarPlot.png") 6 | -------------------------------------------------------------------------------- /recipes/rMpgHistoBinWidth.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | ggplot(df, aes("hwy")) + 4 | geom_histogram(binWidth = 1.5) + 5 | ggsave("media/recipes/rMpgHistoBinWidth.png") 6 | -------------------------------------------------------------------------------- /recipes/rMpgStackedBarPlot.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | ggplot(df, aes("class", fill = "drv")) + 4 | geom_bar() + 5 | ggsave("media/recipes/rMpgStackedBarPlot.png") 6 | -------------------------------------------------------------------------------- /recipes/rLimitXRange.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | ggplot(df, aes("hwy", "cty")) + 4 | geom_point() + 5 | xlim(10.0, 30.0) + 6 | ggsave("media/recipes/rLimitXRange.png") 7 | -------------------------------------------------------------------------------- /recipes/rBothDiscreteAxes.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | ggplot(df, aes("class", "cyl", color = "class")) + 4 | geom_point() + 5 | ggsave("media/recipes/rBothDiscreteAxes.png") 6 | -------------------------------------------------------------------------------- /recipes/rEnlargeXRange.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | ggplot(df, aes("hwy", "cty")) + 4 | geom_point() + 5 | xlim(10.0, 60.0) + 6 | ggsave("media/recipes/rEnlargeXRange.png") 7 | -------------------------------------------------------------------------------- /recipes/rHistogramDensity.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/diamonds.csv") 3 | ggplot(df, aes("carat")) + 4 | geom_histogram(density = true) + 5 | ggsave("media/recipes/rHistogramDensity.png") 6 | -------------------------------------------------------------------------------- /recipes/rStackedMpgFreqpoly.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | ggplot(df, aes("cty", color = "class")) + 4 | geom_freqpoly() + 5 | ggsave("media/recipes/rStackedMpgFreqpoly.png") 6 | -------------------------------------------------------------------------------- /recipes/rStackedMpgHistogram.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | ggplot(df, aes("cty", fill = "class")) + 4 | geom_histogram() + 5 | ggsave("media/recipes/rStackedMpgHistogram.png") 6 | -------------------------------------------------------------------------------- /recipes/rFreqPolyWithAlpha.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | ggplot(df, aes("cty", fill = "class")) + 4 | geom_freqpoly(alpha = 0.3) + 5 | ggsave("media/recipes/rFreqPolyWithAlpha.png") 6 | -------------------------------------------------------------------------------- /recipes/rSimpleGeomText.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | ggplot(df, aes("hwy", "displ")) + 4 | geom_text(aes(text = "manufacturer")) + 5 | ggsave("media/recipes/rSimpleGeomText.png") 6 | -------------------------------------------------------------------------------- /recipes/rMultipleLegends.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | ggplot(df, aes("cty", "displ", size = "cyl", color = "cty")) + 4 | geom_point() + 5 | ggsave("media/recipes/rMultipleLegends.png") 6 | -------------------------------------------------------------------------------- /recipes/rMpgContinuousColorPoints.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | ggplot(df, aes("displ", "hwy", color = "cty")) + 4 | geom_point() + 5 | ggsave("media/recipes/rMpgContinuousColorPoints.png") 6 | -------------------------------------------------------------------------------- /recipes/rMpgHistoNumBins.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | ggplot(df, aes("hwy")) + 4 | geom_histogram(bins = 20) + # by default 30 bins are used 5 | ggsave("media/recipes/rMpgHistoNumBins.png") 6 | -------------------------------------------------------------------------------- /recipes/allRecipes.nim: -------------------------------------------------------------------------------- 1 | import macros 2 | import recipeFiles 3 | 4 | macro importAll(): untyped = 5 | result = newStmtList() 6 | for r in RecipeFiles: 7 | result.add quote do: 8 | import `r` 9 | 10 | importAll() 11 | -------------------------------------------------------------------------------- /recipes/rFormulaAesthetic.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | ggplot(df, aes(f{235 / c"cty"}, "displ")) + 4 | geom_point() + 5 | xlab("cty [L / 100km]") + 6 | ggsave("media/recipes/rFormulaAesthetic.png") 7 | -------------------------------------------------------------------------------- /recipes/rMpgHistoCustomBreaks.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | ggplot(df, aes("hwy")) + 4 | geom_histogram(breaks = @[0'f64, 10, 15, 19, 23, 25, 40]) + 5 | ggsave("media/recipes/rMpgHistoCustomBreaks.png") 6 | -------------------------------------------------------------------------------- /recipes/rCreateMarginBuffer.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | ggplot(df, aes("hwy", "cty")) + 4 | geom_point() + 5 | ylim(5.0, 25.0) + 6 | yMargin(0.1) + 7 | ggsave("media/recipes/rCreateMarginBuffer.png") 8 | -------------------------------------------------------------------------------- /recipes/rLinearFit.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | ggplot(df, aes("displ", "hwy", color = "class")) + 4 | geom_point() + 5 | geom_smooth(smoother = "poly", polyOrder = 1) + 6 | ggsave("media/recipes/rLinearFit.png") 7 | -------------------------------------------------------------------------------- /recipes/rSimpleFacet.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | 3 | let mpg = readCsv("data/mpg.csv") 4 | ggplot(mpg, aes("displ", "hwy")) + 5 | geom_point(aes(color = "manufacturer")) + 6 | facet_wrap(["drv", "cyl"]) + 7 | ggsave("media/recipes/rSimpleFacet.png") 8 | -------------------------------------------------------------------------------- /recipes/rClassifiedGeomText.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | ggplot(df, aes("hwy", "displ", color = "class", size = "cyl")) + 4 | geom_text(aes(text = "manufacturer")) + 5 | ggsave("media/recipes/rClassifiedGeomText.png") 6 | -------------------------------------------------------------------------------- /recipes/rMpgStackedPointPlot.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | ggplot(df, aes("class", color = "drv")) + 4 | geom_point(stat = "count") + 5 | geom_line(stat = "count") + 6 | ggsave("media/recipes/rMpgStackedPointPlot.png") 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all in recipes 2 | /recipes/* 3 | # Unignore all with extensions in recipes 4 | !/recipes/*.* 5 | *~ 6 | # ignore all PDF in media/recipes 7 | media/recipes/*.pdf 8 | media/recipes/*.ppm 9 | media/expected/*.ppm 10 | /recipes/*_json.nim 11 | -------------------------------------------------------------------------------- /recipes/rMpgCustomColorPoint.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | let breaks = @[0'f64, 10, 15, 19, 23, 25, 40] 4 | ggplot(df, aes("displ", "cty")) + 5 | geom_point(color = "#F92672") + 6 | ggsave("media/recipes/rMpgCustomColorPoint.png") 7 | -------------------------------------------------------------------------------- /recipes/rWeightedHistogram.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/diamonds.csv") 3 | ggplot(df, aes("carat", weight = "price")) + 4 | geom_histogram() + 5 | ylab("Binned carat weighted by price") + 6 | ggsave("media/recipes/rWeightedHistogram.png") 7 | -------------------------------------------------------------------------------- /recipes/rMpgDiscreteXScale.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | # coloring by class is of course not required to make this work :) 4 | ggplot(df, aes("cyl", "hwy", color = "class")) + 5 | geom_point() + 6 | ggsave("media/recipes/rMpgDiscreteXScale.png") 7 | -------------------------------------------------------------------------------- /recipes/allRecipesJson.nim: -------------------------------------------------------------------------------- 1 | import macros 2 | import recipeFiles 3 | 4 | macro importAllJson(): untyped = 5 | result = newStmtList() 6 | for r in RecipeFiles: 7 | let rJson = r & "_json" 8 | result.add quote do: 9 | import `rJson` 10 | 11 | importAllJson() 12 | -------------------------------------------------------------------------------- /recipes/rTwoSensorsBadStyle.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, sequtils, seqmath 2 | let df = readCsv("data/50-18004.CSV") 3 | ggplot(df) + 4 | geom_line(aes(x = "in_s", y = "C1_in_V", color = "C1")) + 5 | geom_line(aes(x = "in_s", y = "C2_in_V", color = "C2")) + 6 | ggsave("media/recipes/rTwoSensorsBadStyle.png") 7 | -------------------------------------------------------------------------------- /recipes/rMpgHistoPlusPoints.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | let breaks = @[0'f64, 10, 15, 19, 23, 25, 40] 4 | ggplot(df, aes("cty")) + 5 | geom_histogram(breaks = breaks) + 6 | geom_point(stat="bin", breaks = breaks, binPosition = "center") + 7 | ggsave("media/recipes/rMpgHistoPlusPoints.png") 8 | -------------------------------------------------------------------------------- /recipes/rSimpleVegaLite.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | import ggplotnim/ggplot_vega 3 | let mpg = readCsv("data/mpg.csv") 4 | ggplot(mpg, aes(x = "displ", y = "cty", color = "class")) + 5 | geom_point() + 6 | ggtitle("ggplotnim in Vega-Lite!") + 7 | ggvega("media/recipes/rSimpleVegaLite.html") # w/o arg creates a `/tmp/vega_lite_plot.html` 8 | -------------------------------------------------------------------------------- /recipes/rHistogramOutline.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | ggplot(df, aes("cty", color = "class")) + 4 | geom_histogram(lineWidth = 2.0, 5 | alpha = 0.0, # make transparent (only fill) 6 | hdKind = hdOutline) + # draw as outline 7 | ggsave("media/recipes/rHistogramOutline.png") 8 | -------------------------------------------------------------------------------- /recipes/rPrebinnedHisto.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let bins = @[0, 2, 5, 9, 15] 3 | let counts = @[0.1, 0.8, 0.3, 0.05, 0.0] # <- last element is dummy 4 | let df = toDf({"bin_edges" : bins, "counts" : counts}) 5 | ggplot(df, aes("bin_edges", "counts")) + 6 | geom_histogram(stat = "identity") + 7 | ggsave("media/recipes/rPrebinnedHisto.png") 8 | -------------------------------------------------------------------------------- /recipes/rSimpleLinePlot.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, sequtils, seqmath 2 | 3 | let x = linspace(0.0, 30.0, 1000) 4 | let y = x.mapIt(pow(sin(it), 2.0)) 5 | 6 | let df = toDf(x, y) 7 | 8 | ggplot(df, aes("x", "y")) + # x and y are the identifiers given above as strings 9 | geom_line() + 10 | ggsave("media/recipes/rSimpleLinePlot.png") 11 | -------------------------------------------------------------------------------- /recipes/rLinePlotSize.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, sequtils, seqmath 2 | const 3 | width = 720 4 | height = 480 5 | let x = linspace(0.0, 30.0, 1000) 6 | let y = x.mapIt(pow(sin(it), 2.0)) 7 | let df = toDf(x, y) 8 | ggplot(df, aes("x", "y")) + 9 | geom_line() + 10 | ggsave("media/recipes/rLinePlotSize.png", width = width, height = height) 11 | -------------------------------------------------------------------------------- /playground/randomPlots.nim: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Random plots that are known to work 4 | # works now, identity works too 5 | 6 | ggplot(mpg, aes("cty", color = "class")) + 7 | geom_freqpoly(position = "stack") + 8 | ggsave("figs/1.pdf") 9 | 10 | ggplot(mpg, aes("cty", color = "class")) + 11 | geom_freqpoly(position = "identity") + 12 | ggsave("figs/2.pdf") 13 | -------------------------------------------------------------------------------- /recipes/rTwoSensorsGoodStyle.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, sequtils, seqmath 2 | let df = readCsv("data/50-18004.CSV") 3 | let dfnew = df.gather(["C1_in_V", "C2_in_V"], key = "Channel", value = "V") 4 | # Plotting via `df` directly causes scale problems! 5 | ggplot(dfNew, aes("in_s", "V", color = "Channel")) + 6 | geom_line() + 7 | ggsave("media/recipes/rTwoSensorsGoodStyle.png") 8 | -------------------------------------------------------------------------------- /nim.cfg: -------------------------------------------------------------------------------- 1 | # Set project wide style check to "usages" so that we are only hinted / errored 2 | # for style that mismatches with our initial usage of variables / proc names. 3 | # Otherwise all typical `ggplot2` naming like `geom_point` gets warnings saying 4 | # it should be `geomPoint` according to NEP1 5 | styleCheck:"usages" 6 | 7 | define:"useCairo=true" 8 | define:"useTikZ=true" 9 | -------------------------------------------------------------------------------- /recipes/rBarPlotRotatedLabels.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/fake_shifter_data.txt") 3 | ggplot(df, aes("Shifters", fill = "Year")) + 4 | geom_bar() + 5 | xlab(rotate = -45.0, margin = 1.75, alignTo = "right") + 6 | ggtitle("Number of shifts done by each shifter by year") + 7 | ggsave("media/recipes/rBarPlotRotatedLabels.png", width = 5000, height = 1000) 8 | -------------------------------------------------------------------------------- /benchmarks/million_scatter/million_ggplot2.R: -------------------------------------------------------------------------------- 1 | library(ggplot2) 2 | x <- runif(1000000, min = 0, max = 1.0) 3 | y <- runif(1000000, min = 0, max = 1.0) 4 | df <- data.frame(x, y) 5 | ggplot(df, aes(x, y)) + 6 | geom_point(size = 0.1) + 7 | ggsave("million_ggplot2.pdf") 8 | 9 | # save as pdf 10 | # - 15.5 s 11 | # save as png 12 | # - can't save because of some font error. 13 | -------------------------------------------------------------------------------- /recipes/rNegativeBarPlot.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | 3 | let trials = @["A", "B", "C", "D", "E"] 4 | let values = @[1.0, 0.5, 0, -0.5, -1.0] 5 | 6 | let df = toDf({ "Trial" : trials, 7 | "Value" : values }) 8 | 9 | ggplot(df, aes(x="Trial", y="Value")) + 10 | geom_bar(stat="identity", position="identity") + 11 | ggsave("media/recipes/rNegativeBarPlot.png") 12 | -------------------------------------------------------------------------------- /benchmarks/million_scatter/million_matplotlib_scatter.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | 4 | def main(): 5 | xt = np.random.rand(1_000_000) 6 | yt = np.random.rand(1_000_000) 7 | 8 | plt.scatter(xt, yt, s = 0.1) 9 | plt.savefig("million_matplotlib_scatter.png") 10 | 11 | main() 12 | 13 | # save as pdf 14 | # - 24 s 15 | # save as png 16 | # - 2.8 s 17 | -------------------------------------------------------------------------------- /benchmarks/million_scatter/million_seaborn.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import seaborn 3 | 4 | def main(): 5 | xt = np.random.rand(1_000_000) 6 | yt = np.random.rand(1_000_000) 7 | 8 | ax = seaborn.scatterplot(xt, yt, size = 0.1) 9 | ax.get_figure().savefig("million_matplotlib_seaborn.png") 10 | 11 | main() 12 | 13 | # save as pdf: 14 | # - 136 s 15 | # save as png: 16 | # - 21 s 17 | -------------------------------------------------------------------------------- /bookPlots/readme.org: -------------------------------------------------------------------------------- 1 | * Plots from ggplot2 book 2 | This directory will contain files (one file for each chapter) 3 | containing all plots the =ggplot2= book by Hadley Wickham contains. 4 | 5 | If the code to create the plot deviates from the book, the 6 | corresponding R code is shown in a comment above. Aside from minor 7 | things like using explicit strings in many cases for the =x= and =y= 8 | arguments. 9 | -------------------------------------------------------------------------------- /recipes/rAnnotateUsingGeomText.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | ggplot(df, aes("hwy", "displ")) + 4 | geom_point() + 5 | geom_text(aes(x = f{c"hwy" + 0.3}, 6 | text = "manufacturer"), 7 | alignKind = taLeft, 8 | # font = font(10.0, ...) <- you can also change the font 9 | ) + 10 | ggsave("media/recipes/rAnnotateUsingGeomText.png") 11 | -------------------------------------------------------------------------------- /recipes/rJoyplot.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/gaussSigma_runs.csv") 3 | ggplot(df, aes("bins", "counts")) + 4 | ggridges("Run", overlap = 3.0) + 5 | geom_freqpoly(stat = "identity", color = "white", 6 | size = 2.0) + 7 | margin(top = 3, right = 2.5, bottom = 2) + 8 | theme_void("black") + hideLegend() + 9 | ggsave("media/recipes/rJoyplot.png", width = 1200, height = 1200) 10 | -------------------------------------------------------------------------------- /recipes/rLongTitleMultiline.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, sequtils, seqmath 2 | 3 | let x = linspace(0.0, 30.0, 1000) 4 | let y = x.mapIt(pow(sin(it), 2.0)) 5 | 6 | let df = toDf(x, y) 7 | 8 | ggplot(df, aes("x", "y")) + 9 | geom_line() + 10 | margin(top = 2) + 11 | ggtitle("This is a very long title which gets cropped on the right side as it's longer than the image width.") + 12 | ggsave("media/recipes/rLongTitleMultiline.png") 13 | -------------------------------------------------------------------------------- /recipes/rFormatDecimalsPlot.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, strutils, sequtils, seqmath 2 | 3 | let x = linspace(0.0, 30.0, 1000) 4 | let y = x.mapIt(pow(sin(it), 2.0)) 5 | 6 | let df = toDf(x, y) 7 | 8 | ggplot(df, aes("x", "y")) + 9 | geom_line() + 10 | scale_x_continuous(labels = proc(x: float): string = 11 | x.formatFloat(ffDecimal, 2)) + 12 | xlab(label = " ") + 13 | ggsave("media/recipes/rFormatDecimalsPlot.png") 14 | -------------------------------------------------------------------------------- /recipes/rSimpleRaster.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, random 2 | var 3 | xs = newSeq[float]() 4 | ys = newSeq[float]() 5 | zs = newSeq[float]() 6 | rnd = initRand(42) 7 | for x in countup(-256, 254, 2): 8 | for y in 0 ..< 256: 9 | xs.add x.float 10 | ys.add y.float 11 | zs.add rnd.rand(1.0) 12 | let df = toDf(xs, ys, zs) 13 | ggplot(df, aes("xs", "ys", fill = "zs")) + 14 | geom_raster() + 15 | ggsave("media/recipes/rSimpleRaster.png") 16 | -------------------------------------------------------------------------------- /benchmarks/million_scatter/million_matplotlib.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | 4 | def main(): 5 | xt = np.random.rand(1_000_000) 6 | yt = np.random.rand(1_000_000) 7 | 8 | plt.plot(xt, yt, markersize = 0.1, marker = ".", fillstyle = 'full', 9 | color = "black", linestyle="") 10 | plt.savefig("million_matploltib_plot.pdf") 11 | 12 | main() 13 | 14 | # save as pdf: 15 | # - 22s 16 | # save as png: 17 | # - 1.1 s 18 | -------------------------------------------------------------------------------- /recipes/rCustomBreaks.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, sequtils, seqmath 2 | 3 | let x = linspace(0.0, 30.0, 1000) 4 | let y = x.mapIt(pow(sin(it), 2.0)) 5 | let df = toDf(x, y) 6 | 7 | ggplot(df, aes("x", "y")) + # x and y are the identifiers given above as strings 8 | geom_line() + 9 | scale_x_continuous(breaks = @[0.0, 1.0, 2.0, 12.0]) + # set custom ticks along x 10 | scale_y_continuous(breaks = 50) + # set a custom number of ticks along y 11 | ggsave("media/recipes/rCustomBreaks.png") 12 | -------------------------------------------------------------------------------- /recipes/rSimpleTile.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, random 2 | var 3 | xs = newSeq[float]() 4 | ys = newSeq[float]() 5 | zs = newSeq[float]() 6 | rnd = initRand(42) 7 | for x in 0 ..< 28: 8 | for y in 0 ..< 28: 9 | xs.add x.float 10 | ys.add y.float 11 | zs.add rnd.rand(1.0) 12 | let df = toDf(xs, ys, zs) 13 | ggplot(df, aes("xs", "ys", fill = "zs")) + 14 | geom_tile() + 15 | #scale_x_discrete() + 16 | #scale_y_discrete() + 17 | ggsave("media/recipes/rSimpleTile.png") 18 | -------------------------------------------------------------------------------- /recipes/rRidgeLineGaussBlack.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/gaussSigma_runs.csv") 3 | ggplot(df, aes("bins", "counts", fill = "Run")) + 4 | ggridges("Run", overlap = 3.0) + 5 | geom_freqpoly(stat = "identity", color = "white", 6 | size = 3.0) + 7 | xlab("gaussSigma") + ylab("Counts") + 8 | margin(top = 3, right = 2.5, bottom = 2) + 9 | theme_void("black") + hideLegend() + 10 | ggsave("media/recipes/rRidgeLineGaussBlack.png", width = 1200, height = 1200) 11 | -------------------------------------------------------------------------------- /benchmarks/million_scatter/million_ggplotnim.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | 3 | proc main = 4 | var xt = randomTensor[float](1_000_000, 1.0) 5 | var yt = randomTensor[float](1_000_000, 1.0) 6 | 7 | let df = seqsToDf(xt, yt) 8 | ggplot(df, aes("xt", "yt")) + 9 | geom_point(size = some(0.1)) + 10 | ggsave("million.pdf") 11 | 12 | when isMainModule: 13 | main() 14 | 15 | # compiling with `-d:danger` on arraymancer backend. 16 | # save as pdf: 17 | # - 15.7 s 18 | # save as PNG: 19 | # - 3.6 s 20 | -------------------------------------------------------------------------------- /recipes/rCustomMargins.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | 3 | let 4 | pos = [1, 2, 3, 1, 2, 3] 5 | name = ["a very long long label", "a very long long label", "a very long long label", "b", "b", "b"] 6 | n = [0, 1, 4, 4, 2, 3] 7 | df = toDf(pos, name, n) 8 | 9 | ggplot(df, aes("pos", "name")) + 10 | geom_tile(aes(fill = "n")) + 11 | geom_text(aes(text = "n"), size = 25.0) + 12 | scale_x_discrete() + 13 | scale_y_discrete() + 14 | margin(left = 6.0) + 15 | ggsave("media/recipes/rCustomMargins.png") 16 | -------------------------------------------------------------------------------- /recipes/rErrorBar.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, seqmath, sequtils 2 | # create some polynomial data 3 | let x = linspace(0, 1.0, 10) 4 | let y = x.mapIt(0.5 * it - 1.2 * it * it + 1.1 * it * it * it) 5 | let df = toDf(x, y) 6 | # let's assume we have asymmetric errors, 0.03 down and 0.05 up 7 | ggplot(df, aes("x", "y")) + 8 | geom_point() + 9 | # define errors as a formula, which references our "y" scale 10 | geom_errorbar(aes(yMin = f{`y` - 0.03}, yMax = f{`y` + 0.05})) + 11 | ggsave("media/recipes/rErrorBar.png") 12 | -------------------------------------------------------------------------------- /recipes/rAnnotatedHeatmap.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, math 2 | 3 | let df = readCsv("data/mpg.csv") 4 | let dfRed = df.group_by(["class", "cyl"]).summarize(f{float: "meanHwy" << mean( c"hwy" )}) 5 | # stringification of formula is default name 6 | let meanHwyCol = "meanHwy" 7 | # fill only applies to `tile`, but not text. `color` would apply to text! 8 | ggplot(dfRed, aes("class", "cyl", fill = meanHwyCol)) + 9 | geom_tile() + 10 | geom_text(aes(text = meanHwyCol)) + 11 | scale_y_discrete() + 12 | ggsave("media/recipes/rAnnotatedHeatmap.png") 13 | -------------------------------------------------------------------------------- /data/presidential.csv: -------------------------------------------------------------------------------- 1 | "name","start","end","party" 2 | "Eisenhower",1953-01-20,1961-01-20,"Republican" 3 | "Kennedy",1961-01-20,1963-11-22,"Democratic" 4 | "Johnson",1963-11-22,1969-01-20,"Democratic" 5 | "Nixon",1969-01-20,1974-08-09,"Republican" 6 | "Ford",1974-08-09,1977-01-20,"Republican" 7 | "Carter",1977-01-20,1981-01-20,"Democratic" 8 | "Reagan",1981-01-20,1989-01-20,"Republican" 9 | "Bush",1989-01-20,1993-01-20,"Republican" 10 | "Clinton",1993-01-20,2001-01-20,"Democratic" 11 | "Bush",2001-01-20,2009-01-20,"Republican" 12 | "Obama",2009-01-20,2017-01-20,"Democratic" 13 | -------------------------------------------------------------------------------- /tests/tIssue100.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, unittest 2 | 3 | let dataCsv = """ 4 | w1,w2,w3,w4 5 | 4,0,0,2 6 | 39,1,5,2 7 | 11,1,0,2 8 | """ 9 | 10 | var df = parseCsvString(dataCsv) 11 | df = df.gather(df.getKeys(), "Types", "Values") 12 | try: 13 | ggplot(df, aes("Types", fill = "Values", color = "Values")) + 14 | geom_bar() + ggsave("/tmp/img1.png") 15 | except ValueError as e: 16 | check e.msg == "Cannot perform continuous action using the following formulae: [(Column: Values, kind: scColor), (Column: Values, kind: scFillColor)] in a `count` statistics on column `Types`." 17 | -------------------------------------------------------------------------------- /tests/test_issue5.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, unittest, sequtils 2 | 3 | test "Issue #5 - ragged dataframes": 4 | let x1 = toSeq(0 ..< 2560) 5 | let x2 = toSeq(0 ..< 10) 6 | let df = seqsToDf({ "x1" : x1, 7 | "y1": x1.mapIt(it.float * 1.5), 8 | "x2": x2, 9 | "y2" : x2.mapIt(it.float * 2.2)}) 10 | check df.len == 2560 11 | for k in keys(df): 12 | check df[k].len == 2560 13 | check df["x2", 10 ..< 2560] == %~ toSeq(0 ..< 2550).mapIt(Value(kind: VNull)) 14 | check df["y2", 10 ..< 2560] == %~ toSeq(0 ..< 2550).mapIt(Value(kind: VNull)) 15 | -------------------------------------------------------------------------------- /src/ggplotnim/ggplot_vegatex.nim: -------------------------------------------------------------------------------- 1 | from ../ggplotnim import ggsave, GgPlot, VegaTeX, VegaDraw 2 | import ggplot_vega 3 | export ggplot_vega 4 | 5 | from std / options import some 6 | 7 | proc `+`*(p: GgPlot, d: VegaTeX) = 8 | ## Generates two plots. The TeX version and the Vega-Lite version. 9 | # 1. generate the TeX version 10 | p.ggsave(d.fname & ".tex", texOptions = d.texOptions) 11 | # 2. generate the Vega version 12 | let vegaDraw = VegaDraw(fname: d.fname & ".json", width: d.width, 13 | height: d.height, 14 | asPrettyJson: some(true)) 15 | p + vegaDraw 16 | -------------------------------------------------------------------------------- /recipes/rFacetRaster.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, random 2 | var 3 | xs = newSeq[float]() 4 | ys = newSeq[float]() 5 | zs1 = newSeq[float]() 6 | zs2 = newSeq[float]() 7 | rnd = initRand(42) 8 | for x in 0 ..< 256: 9 | for y in 0 ..< 256: 10 | xs.add x.float 11 | ys.add y.float 12 | zs1.add rnd.rand(1.0) 13 | zs2.add rnd.rand(1.0) 14 | let df = toDf(xs, ys, zs1, zs2) 15 | .gather(["zs1", "zs2"], key = "Map", value = "vals") 16 | ggplot(df, aes("xs", "ys", fill = "vals")) + 17 | facet_wrap("Map") + 18 | xlim(0, 256) + ylim(0, 256) + 19 | geom_raster() + 20 | ggsave("media/recipes/rFacetRaster.png", width = 920) 21 | -------------------------------------------------------------------------------- /recipes/rGeomSmooth.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/commits_nimble.csv") 3 | ggplot(df, aes("days", "count")) + 4 | geom_line() + # plot the raw data as a line 5 | geom_smooth() + # draw a default smoother. This is a Saitzky-Golay filter of 6 | # order 5 and a window `span` of 70% 7 | geom_smooth(smoother = "poly", # add a polynomial smoother using the full range 8 | polyOrder = 7, # of order 7 9 | color = "#FF0000", # and red line (named colors e.g. "red" also allowed) 10 | size = 1.0) + # that is not as thick 11 | ggsave("media/recipes/rGeomSmooth.png") 12 | -------------------------------------------------------------------------------- /recipes/rScaleXDate.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, times 2 | var df = readCsv("data/commits_nimble.csv") 3 | ggplot(df, aes("days", "count")) + 4 | geom_line() + 5 | scale_x_date(isTimestamp = true, # x is unix timestamp 6 | formatString = "MMM-yyyy", # format as 'Jan-1970' 7 | dateSpacing = initDuration(days = 1, # one year is roughly 8 | weeks = 52)) + # 365.25 days, but we use 365 9 | geom_smooth(span = 0.64) + 10 | ylab("Commit count") + xlab("Date") + 11 | ggtitle("Daily commit counts in all nimble repositories") + 12 | ggsave("media/recipes/rScaleXDate.png") 13 | -------------------------------------------------------------------------------- /recipes/rFacetTpa.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/run_305_tpa_data.csv") 3 | # gather all columns to a long format df 4 | echo df 5 | let dfLong = df.gather(getKeys(df), key = "Property", value = "Value") 6 | ggplot(dfLong, aes("Value")) + 7 | facet_wrap("Property", 8 | scales = "free") + # each property has very different data ranges, Leave free 9 | geom_histogram(bins = 100, position = "identity", 10 | binBy = "subset") + # `binBy subset` means the histogram will be calculated 11 | # in the data range of each properties data range 12 | ggsave("media/recipes/rFacetTpa.png", width = 800, height = 600) 13 | -------------------------------------------------------------------------------- /recipes/rCustomFill.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, sequtils, seqmath, chroma, tables 2 | 3 | let 4 | pos = [1, 2, 3, 1, 2, 3] 5 | name = ["a", "a", "a", "b", "b", "b"] 6 | n = [0, 1, 4, 4, 2, 3] 7 | df = toDf(pos, name, n) 8 | 9 | ggplot(df, aes("pos", "name")) + 10 | geom_tile(aes(fill = "n")) + 11 | geom_text(aes(text = "n"), size = 25.0) + 12 | scale_x_discrete() + 13 | scale_y_discrete() + 14 | scale_fill_manual({ 0 : color(1.0, 0.0, 0.0), 15 | 1 : color(0.0, 1.0, 0.0), 16 | 2 : color(0.0, 0.0, 1.0), 17 | 3 : color(1.0, 1.0, 0.0), 18 | 4 : color(1.0, 0.0, 1.0) }.toTable) + 19 | ggsave("media/recipes/rCustomFill.png") 20 | -------------------------------------------------------------------------------- /recipes/rAnnotateMaxValues.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | let df = readCsv("data/mpg.csv") 3 | let dfMax = df.mutate(f{"mpgMean" ~ (`cty` + `hwy`) / 2.0}) 4 | .arrange("mpgMean") 5 | .tail(1) 6 | ggplot(df, aes("hwy", "displ")) + 7 | geom_point(aes(color = "cty")) + # set point specific color mapping 8 | # Add the annotation for the car model below the point 9 | geom_text(data = dfMax, 10 | aes = aes(y = f{c"displ" - 0.2}, 11 | text = "model")) + 12 | # and add another annotation of the mean mpg above the point 13 | geom_text(data = dfMax, 14 | aes = aes(y = f{c"displ" + 0.2}, 15 | text = "mpgMean")) + 16 | ggsave("media/recipes/rAnnotateMaxValues.png") 17 | -------------------------------------------------------------------------------- /recipes/rFormatDatesPlot.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, strutils, sequtils, seqmath, times 2 | 3 | let x = linspace(0.0, 30.0, 1000) 4 | let y = x.mapIt(pow(sin(it), 2.0)) 5 | 6 | let df = toDf(x, y) 7 | 8 | # helper template to get a reproducible `DateTime` for CI! 9 | template nowTmpl(): untyped = initDateTime(15, mMay, 2020, 00, 00, 00, 00, utc()) 10 | 11 | ggplot(df, aes("x", "y")) + 12 | geom_line() + 13 | scale_y_continuous(labels = proc(x: float): string = 14 | x.formatFloat(ffDecimal, 1)) + 15 | scale_x_continuous(labels = proc(x: float): string = 16 | getDateStr(nowTmpl() - int(x).months)) + 17 | xlab(label = " ") + 18 | ggsave("media/recipes/rFormatDatesPlot.png") 19 | -------------------------------------------------------------------------------- /tests/tvega.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | import ../src/ggplotnim 3 | import ../src/ggplotnim/ggplot_vega 4 | 5 | suite "Vega-Lite backend": 6 | let mpg = readCsv("data/mpg.csv") 7 | let plt = ggplot(mpg, aes(x = "displ", y = "cty", color = "class")) + 8 | geom_point() + 9 | ggtitle("ggplotnim in Vega-Lite!") 10 | 11 | test "Raises on unsupported file types": 12 | try: 13 | plt + ggvega("test.foo", show = false) 14 | except VegaError: 15 | discard 16 | 17 | test "Generation of only HTML string": 18 | ## TODO: turn this into a proper test! 19 | doAssert toVegaHtml(plt).len > 0 20 | doAssert toVegaHtml(plt, onlyBody = true).len > 0 21 | doAssert toVegaHtml(plt, onlyBody = true, pretty = true).len > 0 22 | let body = toVegaHtml(plt, onlyBody = true) 23 | let html = embedVegaBody(body) 24 | doAssert html.len > body.len 25 | -------------------------------------------------------------------------------- /recipes/rBarPlotCompStats.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, sequtils, seqmath, strutils 2 | let cols = toSeq(0 .. 7).mapIt($it) 3 | # make `parseInt` work on Values, so we can parse the long form 4 | # `Channel` column 5 | #liftScalarStringProc(parseInt) 6 | let df = readCsv("data/szinti_channel_counts.txt", 7 | sep = '\t', 8 | colNames = cols) 9 | .gather(cols, key = "Channel", value = "Count") 10 | .mutate(f{string -> int: "Channel" ~ parseInt( df["Channel"][idx] )}) 11 | let dfMean = df.group_by("Channel").summarize(f{float: "Mean counts / min" << mean( c"Count" )}) 12 | # calculate mean for each channel 13 | ggplot(dfMean, aes("Channel", "Mean counts / min")) + 14 | geom_bar(stat = "identity", position = "identity") + 15 | scale_x_discrete(name = "Channel number") + 16 | ggtitle("Mean counts per channel") + 17 | ggsave("media/recipes/rBarPlotCompStats.png") 18 | -------------------------------------------------------------------------------- /recipes/rHighlightMinMax.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, algorithm 2 | # base this on one of the above examples 3 | let df = readCsv("data/50-18004.CSV") 4 | .gather(["C1_in_V", "C2_in_V"], key = "Channel", value = "V") 5 | # filter to Channel 2 and sort by voltage 6 | let dfSorted = df.filter(f{c"Channel" == "C2_in_V"}) 7 | .arrange("V", SortOrder.Descending) 8 | # get min and max 9 | let dfMax = dfSorted.head(1) 10 | let dfMin = dfSorted.tail(1) 11 | ggplot(df, aes("in_s", "V", color = "Channel")) + 12 | geom_line() + # the actual data 13 | # add additional geom with `data =` arg and set styles. 14 | geom_point(data = dfMax, 15 | color = "#FF0000", # named colors (e.g. "red") are also possible! 16 | size = 5.0, 17 | marker = mkCross) + 18 | geom_point(data = dfMin, 19 | color = "#0000FF", 20 | size = 5.0) + 21 | ggsave("media/recipes/rHighlightMinMax.png") 22 | -------------------------------------------------------------------------------- /recipes/rCustomColormap.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | import seqmath # for gauss 3 | 4 | # generate some gaussian 2D data 5 | var 6 | xs = newSeq[float]() 7 | ys = newSeq[float]() 8 | zs = newSeq[float]() 9 | let coords = linspace(-1.0, 1.0, 200) 10 | for y in coords: 11 | for x in coords: 12 | xs.add x 13 | ys.add y 14 | zs.add gauss(sqrt(x*x + y*y), mean = 0.0, sigma = 0.2) # small sigma to cover multiple σ 15 | 16 | # get the Inferno colormap 17 | var customInferno = inferno() 18 | customInferno.name = "InfernoWithTransparent" 19 | # and assign the 0th element to exact transparent 20 | customInferno.colors[0] = 0 # transparent 21 | 22 | ggplot(toDf(xs, ys, zs), aes("xs", "ys", fill = "zs")) + 23 | geom_raster() + 24 | xlim(-1, 1) + ylim(-1, 1) + 25 | scale_fill_gradient(customInferno) + 26 | ggtitle("Modified Inferno colormap with 0 set to transparent") + 27 | ggsave("media/recipes/rCustomColormap.png") 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | os: 4 | - linux 5 | 6 | language: c 7 | dist: xenial 8 | 9 | matrix: 10 | include: 11 | # Build and test against the master (stable) and devel branches of Nim 12 | # Build and test using both gcc and clang 13 | - os: linux 14 | env: CHANNEL=stable 15 | compiler: gcc 16 | 17 | - os: linux 18 | env: CHANNEL=devel 19 | compiler: gcc 20 | 21 | cache: 22 | directories: 23 | - "$HOME/.nimble" 24 | - "$HOME/.choosenim" 25 | 26 | addons: 27 | apt: 28 | packages: 29 | - libcairo2 30 | - libcairo2-dev 31 | - imagemagick 32 | 33 | install: 34 | - curl https://nim-lang.org/choosenim/init.sh -sSf > init.sh 35 | - sh init.sh -y 36 | - export PATH=$HOME/.nimble/bin:$PATH 37 | - nimble refresh -y 38 | - choosenim $CHANNEL 39 | - nim --version 40 | - nimble install ntangle -y 41 | 42 | script: 43 | # limit our stack size to avoid regressing after PR #36 w/o noticing 44 | - ulimit -s 1024 45 | - nimble install -y 46 | - nimble -y fulltest 47 | - nimble -y testCI 48 | -------------------------------------------------------------------------------- /recipes/rAutoColoredNeuralSpikes.nim: -------------------------------------------------------------------------------- 1 | # first start with auto selection of colors 2 | import ggplotnim, sequtils 3 | const numx = 50 4 | const numy = 8 5 | const lineSizes = [0.4, 0.3, 0.2, 0.8, 0.5, 0.6, 0.7, 0.9] 6 | # NOTE: The creation of the data here could surely be done in a nicer 7 | # way... 8 | var spikes = newSeq[float]() 9 | var sizes = newSeq[float]() 10 | for y in 0 ..< numy: 11 | for x in 0 ..< numx: 12 | spikes.add y.float 13 | sizes.add lineSizes[y] 14 | var df = newDataFrame() 15 | df["spikes"] = toColumn spikes 16 | df["neurons"] = toColumn randomTensor(numx * numy, 1.0) 17 | df["lineSize"] = toColumn sizes 18 | 19 | ggplot(df, aes("neurons", "spikes", color = factor("lineSize"))) + 20 | geom_linerange(aes(ymin = f{c"spikes" - c"lineSize" / 2.0}, 21 | ymax = f{c"spikes" + c"lineSize" / 2.0})) + 22 | scale_y_continuous() + # make sure y is considered cont. 23 | ylim(-1, 8) + # at the moment ymin, ymax are not considered for the plot range (that's a bug) 24 | ggtitle("Spike raster plot") + 25 | ggsave("media/recipes/rAutoColoredNeuralSpikes.png") 26 | -------------------------------------------------------------------------------- /recipes/rColormaps.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, sequtils 2 | 3 | # 1000 points to use 4 | let xs = linspace(0.0, 1.0, 1000) 5 | var plts: seq[GgPlot] 6 | # generate data to show a gradient for (currently a single element in one 7 | # axis isn't supported for `geom_raster`, as it's essentially discrete). 8 | var df = newDataFrame() 9 | for i in 0 ..< 5: 10 | df.add toDf({"x" : xs, "y" : i }) 11 | 12 | for scale in [viridis(), magma(), plasma(), inferno()]: 13 | plts.add ggplot(df, aes("x", "y", fill = "x"), backend = bkCairo, fType = fkPng) + 14 | scale_fill_gradient(scale) + # assign the correct scale 15 | geom_raster() + 16 | ylim(0, 5) + # due to our weird data (height deduced as 1, set correct limits) 17 | theme_void() + # no scales 18 | hideLegend() + # no legend 19 | margin(top = 1, bottom = 0.3, right = 0.3) + # set small margin left / bottom 20 | ggtitle("Colorscale: " & $scale.name) 21 | # now create a multi plot of all of them 22 | ggmulti(plts, "media/recipes/rColormaps.png", widths = @[600], heights = @[150, 150, 150, 150], 23 | width = 600, height = 600) 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Vindaar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /benchmarks/bench_many_geoms.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | import random 3 | import tables 4 | import math 5 | 6 | const paths = 1000 7 | const dates = 80 8 | 9 | proc gaussian*(rnd: var Rand, mu = 0.0, sigma = 1.0): float = 10 | var 11 | s = 0.0 12 | u = 0.0 13 | v = 0.0 14 | while s >= 1.0 or s <= 0.0: 15 | u = 2.0 * rnd.rand(0.0..1.0) - 1.0 16 | v = 2.0 * rnd.rand(0.0..1.0) - 1.0 17 | s = (u * u) + (v * v) 18 | 19 | let x = u * sqrt(-2.0 * ln(s) / s) 20 | return (mu + (sigma * x)) 21 | 22 | proc createDataFrame(): DataFrame = 23 | const sigma = 0.10 24 | result = newDataFrame() 25 | var rnd = initRand(124325) 26 | var simPath = newSeq[float](dates) 27 | for j in 0.. float: getMean(`bins`, `counts`)}), # compute the mean of the current Run 26 | color = "#FF0000") + # color the line red 27 | margin(top = 2) + # increase top margin due to large overlap 28 | xlab("gaussSigma") + ylab("Counts") + 29 | ggsave("media/recipes/rRidgeLineGauss.png", width = 1200, height = 1200) 30 | -------------------------------------------------------------------------------- /recipes/rDiscreteXLine.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, seqmath 2 | import random 3 | 4 | const paths = 10 5 | const dates = 80 6 | 7 | proc gaussian*(rnd: var Rand, mu = 0.0, sigma = 1.0): float = 8 | var 9 | s = 0.0 10 | u = 0.0 11 | v = 0.0 12 | while s >= 1.0 or s <= 0.0: 13 | u = 2.0 * rnd.rand(0.0..1.0) - 1.0 14 | v = 2.0 * rnd.rand(0.0..1.0) - 1.0 15 | s = (u * u) + (v * v) 16 | 17 | let x = u * sqrt(-2.0 * ln(s) / s) 18 | return (mu + (sigma * x)) 19 | 20 | proc createDataFrame(): DataFrame = 21 | const sigma = 0.10 22 | var rnd = initRand(124325) 23 | var pathNames = newSeq[string](dates * paths) 24 | var pathVals = newSeq[float](dates * paths) 25 | var tenors = newSeq[int](dates * paths) 26 | for j in 0 ..< paths: 27 | pathVals[j * dates] = 100.0 28 | pathNames[j * dates] = "path" & $(j + 1) 29 | tenors[j * dates] = 0 30 | for i in 1 ..< dates: 31 | let idx = j * dates + i 32 | pathNames[idx] = "path" & $(j + 1) 33 | pathVals[idx] = (pathVals[idx - 1] * exp(-0.5 * sigma * sigma + sigma * gaussian(rnd))) 34 | tenors[idx] = i 35 | result = toDf({ "tenors" : tenors, 36 | "pathNames" : pathNames, 37 | "pathValues" : pathVals }) 38 | 39 | let df = createDataFrame() 40 | ggplot(df, aes("tenors", "pathValues", color = "pathNames")) + 41 | geom_line() + 42 | ggsave("media/recipes/rDiscreteXLine.png") 43 | -------------------------------------------------------------------------------- /recipes/rCustomColoredNeuralSpikes.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, sequtils 2 | const numx = 50 3 | const numy = 8 4 | const lineSizes = [0.4, 0.3, 0.2, 0.8, 0.5, 0.6, 0.7, 0.9] 5 | # alternatively using fixed colors and one geom_linerange for each color 6 | let colorCodes = @[color(0, 0, 0), 7 | color(1, 0, 0), 8 | color(0, 1, 0), 9 | color(0, 0, 1), 10 | color(1 , 1, 0), 11 | color(1, 0, 1), 12 | color(0, 1, 1), 13 | color(1, 0, 1)] 14 | var df = newDataFrame() 15 | for nr in 0 ..< numy: 16 | df["neuron " & $nr] = toColumn randomTensor(numx, 1.0) 17 | var plt = ggplot(df) 18 | for nr in 0 ..< numy: 19 | # could combine with above loop, but for clarity 20 | plt = plt + geom_linerange(aes(x = ("neuron " & $nr), 21 | y = nr, 22 | ymin = nr.float - lineSizes[nr] / 2.0, 23 | ymax = nr.float + lineSizes[nr] / 2.0), 24 | color = colorCodes[nr]) 25 | # finally add scales, title and plot 26 | plt + scale_y_continuous() + # make sure y is considered cont. 27 | ylim(-1, 8) + # at the moment ymin, ymax are not considered for the plot range (that's a bug) 28 | xlab("Neurons") + 29 | ylab("Spikes") + 30 | ggtitle("Spike raster plot, manual colors") + 31 | ggsave("media/recipes/rCustomColoredNeuralSpikes.png") 32 | -------------------------------------------------------------------------------- /bookPlots/weirdFastSlow.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, times 2 | 3 | proc fastRead() = 4 | let csvD = readCsv("data/diamonds.csv") 5 | let df = toDf(csvD) 6 | echo df 7 | 8 | proc fastPlot() = 9 | let mpg = toDf(readCsv("data/mpg.csv")) 10 | ggplot(mpg, aes(x = "displ", y = "hwy")) + 11 | geom_point() + 12 | ggsave("figs/2.3_1.pdf") 13 | ggplot(mpg, aes("displ", "hwy")) + 14 | geom_point() + 15 | ggsave("figs/2.3_2.pdf") 16 | ggplot(mpg, aes("cty", "hwy")) + 17 | geom_point() + 18 | ggsave("figs/2.3_4.pdf") 19 | 20 | proc fastTogether() = 21 | fastPlot() 22 | fastRead() 23 | 24 | proc slowTogether() = 25 | block: 26 | let mpg = toDf(readCsv("data/mpg.csv")) 27 | ggplot(mpg, aes(x = "displ", y = "hwy")) + 28 | geom_point() + 29 | ggsave("figs/2.3_1.pdf") 30 | ggplot(mpg, aes("displ", "hwy")) + 31 | geom_point() + 32 | ggsave("figs/2.3_2.pdf") 33 | ggplot(mpg, aes("cty", "hwy")) + 34 | geom_point() + 35 | ggsave("figs/2.3_4.pdf") 36 | block: 37 | let csvD = readCsv("data/diamonds.csv") 38 | let df = toDf(csvD) 39 | echo df 40 | 41 | when isMainModule: 42 | let t0 = epochTime() 43 | fastPlot() 44 | let t1 = epochTime() 45 | echo "Plot first: ", (t1 - t0) 46 | fastRead() 47 | let t2 = epochTime() 48 | echo "Read first: ", (t2 - t1) 49 | fastTogether() 50 | let t3 = epochTime() 51 | echo "Fast together ", (t3 - t2) 52 | slowTogether() 53 | let t4 = epochTime() 54 | echo "Together ", (t4 - t3) 55 | -------------------------------------------------------------------------------- /tests/test_issue2.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, ginger 2 | import seqmath, sequtils 3 | import unittest 4 | 5 | const Backend = when defined(noCairo): bkDummy else: bkCairo 6 | 7 | test "Issue2": 8 | let xdata = toSeq(0 ..< 2560) 9 | let ydata = xdata.mapIt(it.float * it.float) 10 | 11 | let plt = ggplot(seqsToDf({ "x" : xdata, 12 | "y" : ydata }), 13 | aes("x", "y"), 14 | backend = Backend, fType = fkSvg) + 15 | geom_line() 16 | 17 | let plotView = plt.ggcreate() 18 | let view = plotView.view 19 | let xScale = (low: 0.0, high: 3000.0) 20 | let yScale = (low: 0.0, high: 7000000.0) 21 | for ch in view.children: 22 | if ch.name == "plot": 23 | check ch.xScale == xScale # tick calculation modifies max 24 | check ch.yScale == yScale 25 | for el in ch.children: 26 | if el.name == "data": 27 | # data child must inherit the new scales from its plot parent 28 | check el.xScale == xScale 29 | check el.yScale == yScale 30 | for p in el.objects: 31 | # plot scales also must inherit its parent's scale 32 | check p.kind == goPolyLine 33 | for xy in p.plPos: 34 | check xy.x.kind == ukData 35 | check xy.x.scale == xScale 36 | check xy.y.scale == yScale 37 | 38 | # the scale assigned to the total plot does not have to be the one finally 39 | # TODO: decide if we want to make sure to update it regardless 40 | #check view.xScale == (low: 0.0, high: 3000.0) # tick calculation modifies max 41 | check view.yScale == yScale 42 | plt.ggsave("test_issue2.pdf") 43 | -------------------------------------------------------------------------------- /recipes/rCustomAnnotations.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | import algorithm, sequtils, strformat, strutils 3 | # get the data from one of the other recipes 4 | let df = readCsv("data/50-18004.CSV") 5 | let dfnew = df.gather(["C1_in_V", "C2_in_V"], key = "Channel", value = "V") 6 | # assume we want to create an annotation that prints the largest 5 values of 7 | # Channel 2; get largest values, sorted by time (`in_s`) 8 | let dfChMax = dfNew.filter(f{c"Channel" == "C2_in_V"}) 9 | .arrange("V", SortOrder.Descending) 10 | .head(5) 11 | .arrange("in_s") # sort again by x axis 12 | # build an annotation: 13 | var annot: string 14 | let idxs = toSeq({'A'..'E'}) 15 | for j, id in idxs: 16 | let xVal = alignLeft(formatFloat(dfChMax["in_s", j, float], precision = 2), 9) 17 | let yVal = formatFloat(dfChMax["V", j, float], precision = 4) 18 | annot.add &"{id}: (x: {xVal}, y: {yVal})" 19 | if j < idxs.high: 20 | annot.add "\n" 21 | # create a font to use using the `ggplotnim.font` helper 22 | let font = font(12.0, family = "monospace") 23 | # now create the plot and put the annotation where we want it 24 | ggplot(dfNew, aes("in_s", "V", color = "Channel")) + 25 | geom_line() + 26 | # either for instance in relative coordinates of the plot viewport 27 | # Values smaller 0.0 or larger 1.0 work too. Puts the annotation outside 28 | # of the plot 29 | annotate(annot, 30 | left = 0.5, 31 | bottom = 1.0, 32 | font = font) + 33 | # or in data coordinates using `(x, y)` 34 | annotate(annot, 35 | x = -2e-6, 36 | y = 0.06, 37 | font = font, 38 | backgroundColor = parseHex("FFEBB7")) + 39 | ggsave("media/recipes/rCustomAnnotations.png") 40 | -------------------------------------------------------------------------------- /tests/testVega.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | import ggplotnim/vega_utils 3 | 4 | import unittest, json 5 | 6 | import monocle 7 | 8 | suite "Vega-lite backend tests": 9 | test "vega-scatter plot": 10 | let expected = parseJson(""" 11 | { 12 | "$schema": "https://vega.github.io/schema/vega-lite/v4.json", 13 | "description" : "Vega-lite plot created by ggplotnim", 14 | "width" : 640, 15 | "height" : 480, 16 | "title": "ggplotnim - or I Suck At Naming Things", 17 | "data": {"values" : []}, 18 | "mark": "point", 19 | "encoding": { 20 | "x": {"field": "displ", "type": "quantitative"}, 21 | "y": {"field": "cty", "type": "quantitative"}, 22 | "color": {"field": "class", "type": "nominal"} 23 | } 24 | } 25 | """) 26 | let expDataEx = parseJson("""[ 27 | {"displ": 1.8, "cty": 18.0, "class": "compact"}, 28 | {"displ": 1.8, "cty": 21.0, "class": "compact"}, 29 | {"displ": 2.0, "cty": 20.0, "class": "compact"} ] 30 | """) 31 | let mpg = toDf(readCsv("data/mpg.csv")) 32 | let vegaJson = ggplot(mpg, aes(x = "displ", y = "cty", color = "class")) + 33 | geom_point() + 34 | ggtitle("ggplotnim - or I Suck At Naming Things™") + 35 | ggvega() 36 | check vegaJson["$schema"] == expected["$schema"] 37 | check vegaJson["description"] == expected["description"] 38 | # check vegaJson["data"] == expected["data"] 39 | check vegaJson["mark"] == expected["mark"] 40 | check vegaJson["encoding"] == expected["encoding"] 41 | check vegaJson["data"].kind == expected["data"].kind 42 | for i in 0 .. 2: 43 | check vegaJson["data"]["values"][i] == expDataEx[i] 44 | check vegaJson["data"]["values"].len == 234 45 | check vegaJson.len == expected.len 46 | show(vegaJson) 47 | -------------------------------------------------------------------------------- /recipes/rMassAttenuationFunction.nim: -------------------------------------------------------------------------------- 1 | import sequtils, seqmath, ggplotnim 2 | 3 | proc logMassAttenuation(e: float): float = 4 | ## calculates the logarithm of the mass attenuation coefficient for a given 5 | ## energy `e` in `keV` and the result in `cm^2/g` 6 | result = -1.5832 + 5.9195 * exp(-0.353808 * e) + 4.03598 * exp(-0.970557 * e) 7 | 8 | let energies = linspace(0.0, 10.0 , 1000) # from 0 to 10 keV 9 | let logMuOverRho = energies.mapIt(logMassAttenuation(it)) 10 | # now the non-log values 11 | let muOverRho = logMuOverRho.mapIt(exp(it)) 12 | 13 | const massAttenuationFile = "data/mass_attenuation_nist_data.txt" 14 | # skip one line after header, second header line 15 | var dfMuRhoTab = readCsv(massAttenuationFile, header = "#", 16 | sep = ' ') 17 | # convert MeV energy to keV 18 | .mutate(f{"Energy" ~ c"Energy" * 1000.0}) 19 | .filter(f{float: c"Energy" >= energies.min and c"Energy" <= energies.max}) 20 | # create df of interpolated values 21 | let dfMuRhoInterp = toDf({ "E / keV" : energies, 22 | "mu/rho" : muOverRho }) 23 | # rename the columns of the tabulated values df and create a log column 24 | dfMuRhoTab = dfMuRhoTab.rename(f{"E / keV" <- "Energy"}) 25 | # build combined DF 26 | let dfMuRho = bind_rows([("Interpolation", dfMuRhoInterp), 27 | ("NIST", dfMuRhoTab)], 28 | id = "type") 29 | 30 | # and the plot of the raw mu/rho values 31 | ggplot(dfMuRho, aes("E / keV", "mu/rho", color = "type")) + 32 | geom_line(data = dfMuRho.filter(f{`type` == "Interpolation"})) + 33 | geom_point(data = dfMuRho.filter(f{`type` == "NIST"})) + 34 | scale_y_log10() + 35 | ggtitle("Mass attenuation coefficient interpolation and data") + 36 | ggsave("media/recipes/rMassAttenuationFunction.png") 37 | -------------------------------------------------------------------------------- /recipes/rTikZLandau.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, math, sequtils, latexdsl, strutils, ginger 2 | proc landauApprox(x: float): float = 3 | result = 1.0 / sqrt(2 * math.PI) * exp(- (x + exp(-x)) / 2 ) 4 | 5 | proc annotateText(): string = 6 | # pure math in raw string due to too many invalid nim constructs for `latex` 7 | # macro (would result in ugly mix of strings and identifiers) 8 | # Need manual math mode via `$`! 9 | let eq = r"$p(x) = \frac{1}{2πi}\int_{a-i∞}^{a+i∞} e^{s \log(s) + xs}\, ds$" 10 | let eqApprox = r"$p(x) \approx \frac{1}{\sqrt{2π}} \exp\left(- \frac{x + e^{-x}}{2} \right)$" 11 | # align text with math using 2 line breaks for better separation (single line break is too 12 | # squished. Cannot use `equation` or similar in a TikZ node afaiu :/) 13 | result = r"The Landau distribution\\ \\ " & 14 | eq & r"\\ \\" & 15 | r"reduces to the approximation:\\ \\ " & 16 | eqApprox & r"\\ \\ " & 17 | r"for $μ = 0, c = 1$" 18 | 19 | let x = linspace(-5.0, 15.0, 1000) 20 | let y = x.mapIt(landauApprox(it)) 21 | let df = toDf(x, y) 22 | ggplot(df, aes("x", "y")) + 23 | geom_line() + # draw our Landau data as a line 24 | annotate(annotateText(), # add our text annotation 25 | x = 5.0, y = 0.1, # at this location in 'data space' 26 | backgroundColor = transparent) + # transparent background as we do manual TeX line breaks 27 | ggtitle(r"Approximation of Landau distribution: " & 28 | r"$p(x) \approx \frac{1}{\sqrt{2π}} \exp\left(- \frac{x + e^{-x}}{2} \right)$", 29 | titleFont = font(12.0)) + 30 | ggsave("media/recipes/rTikZLandau.tex", standalone = true) # standalone to get TeX file w/ only plot 31 | # use: 32 | # ggsave("media/recipes/rTikZLandau.pdf", useTex = true, standalone = true) 33 | # to directly compile to standalone PDF (using local xelatex) 34 | -------------------------------------------------------------------------------- /recipes/runRecipes.nim: -------------------------------------------------------------------------------- 1 | import shell, os, macros, times, cligen 2 | 3 | import recipeFiles 4 | 5 | macro genCommands(prefix: static string, 6 | suffix: static string = ""): untyped = 7 | var cmds = newStmtList() 8 | for r in RecipeFiles: 9 | let cmd = prefix & r & suffix 10 | cmds.add quote do: 11 | `cmd` 12 | echo cmds.repr 13 | result = quote do: 14 | let res = shellVerbose: 15 | `cmds` 16 | if res[1] != 0: 17 | raise newException(Exception, "Failed to build or run at least one recipe!") 18 | 19 | template printWarning(): untyped = 20 | echo "WARNING: Using `runRecipes` to compile and run all recipes is deprecated, " 21 | echo "because it leads to extremely bad performance, due to the overhead of " 22 | echo "compiling each module indepentently! Compile and run `allRecipes.nim` instead!" 23 | 24 | proc main(compile = false, 25 | compileDanger = false, 26 | run = false, 27 | json = false) = 28 | if (not compile and not compileDanger and not run and not json) or 29 | (compile and run): 30 | let t0 = epochTime() 31 | printWarning() 32 | genCommands("nim c -d:nimLegacyRandomInitRand -r recipes/", ".nim") 33 | echo "Compiling and running all recipes took ", epochTime() - t0 34 | elif compileDanger and run: 35 | let t0 = epochTime() 36 | printWarning() 37 | genCommands("nim c -d:danger -d:nimLegacyRandomInitRand -r recipes/", ".nim") 38 | echo "Compiling and running (danger mode) all recipes took ", epochTime() - t0 39 | elif compile: 40 | printWarning() 41 | genCommands("nim c -d:nimLegacyRandomInitRand recipes/", ".nim") 42 | elif compileDanger: 43 | printWarning() 44 | genCommands("nim c -d:danger -d:nimLegacyRandomInitRand recipes/", ".nim") 45 | elif json and run: 46 | let t0 = epochTime() 47 | for r in RecipeFiles: 48 | generateJsonFile(r, toRun = true) 49 | echo "Generating all source files to generate JSON and running them took ", epochTime() - t0 50 | elif json: 51 | let t0 = epochTime() 52 | for r in RecipeFiles: 53 | generateJsonFile(r) 54 | echo "Generating all source files to generate JSON took ", epochTime() - t0 55 | elif run: 56 | let t0 = epochTime() 57 | genCommands("./recipes/") 58 | echo "Running all recipes took ", epochTime() - t0 59 | else: 60 | doAssert false 61 | 62 | when isMainModule: 63 | dispatch main 64 | -------------------------------------------------------------------------------- /recipes/rPointInPolygons.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, sequtils, chroma, options, sugar, random 2 | 3 | type 4 | Point = object 5 | x, y: float 6 | Vertex {.borrow: `.`.} = distinct Point 7 | Polygon = object 8 | vertices: seq[Vertex] 9 | 10 | func len(p: Polygon): int = p.vertices.len 11 | func `[]`(p: Polygon, idx: int): Vertex = p.vertices[idx] 12 | 13 | proc initPolygon[T](vs: varargs[tuple[x, y: T]]): Polygon = 14 | result.vertices = newSeq[Vertex](vs.len) 15 | for i, v in vs: 16 | result.vertices[i] = Point(x: v[0].float, y: v[1].float).Vertex 17 | 18 | proc flatten(p: Polygon): (seq[float], seq[float]) = 19 | result = (p.vertices.mapIt(it.x), p.vertices.mapIt(it.y)) 20 | # add first point to get proper drawn polygon with geom line! 21 | result[0].add p.vertices[0].x 22 | result[1].add p.vertices[0].y 23 | 24 | proc inPolygon(p: Point, poly: Polygon): bool = 25 | # based on: https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html 26 | var j = poly.len - 1 27 | for i in 0 ..< poly.vertices.len: 28 | if ((poly[i].y <= p.y and p.y < poly[j].y) or 29 | (poly[j].y <= p.y and p.y < poly[i].y)) and 30 | (p.x < (poly[j].x - poly[i].x) * (p.y - poly[i].y) / (poly[j].y - poly[i].y) + poly[i].x): 31 | result = not result 32 | j = i 33 | 34 | proc inAnyPolygon(p: Point, polys: seq[Polygon]): bool = 35 | for poly in polys: 36 | if p.inPolygon(poly): return true 37 | 38 | let p1 = initPolygon((0, 1), (6, 0), (5, 2), (4, 1), (2, 4)) 39 | let p2 = initPolygon((5, 4), (8, 10), (10, 2), (7, 4)) 40 | 41 | let df1 = toDf({ "x" : p1.flatten[0], 42 | "y" : p1.flatten[1] }) 43 | let df2 = toDf({ "x" : p2.flatten[0], 44 | "y" : p2.flatten[1] }) 45 | let df = bind_rows(("Polygon 1", df1), ("Polygon 2", df2), "Num") 46 | 47 | var rnd = initRand(42) 48 | # now sample a bunch of points in (0, 10) plane and plot it 49 | let points = collect(newSeq): 50 | for i in 0 ..< 300: 51 | Point(x: rnd.rand(10.0), y: rnd.rand(10.0)) 52 | let inPoly = points.mapIt(it.inAnyPolygon(@[p1, p2])) 53 | 54 | let dfPoints = toDf({ "x" : points.mapIt(it.x), 55 | "y" : points.mapIt(it.y), 56 | "InPoly" : inPoly }) 57 | 58 | # TODO: results in vertical line at start of polygon 59 | ggplot(df, aes(x, y)) + 60 | geom_line(aes = aes(fill = "Num"), fillColor = "#ebba34") + 61 | geom_point(data = dfPoints, aes = aes(color = "InPoly")) + 62 | ggsave("./media/recipes/rPointInPolygons.png") 63 | -------------------------------------------------------------------------------- /recipes/rAxionMassesLogLog.nim: -------------------------------------------------------------------------------- 1 | import sequtils, seqmath, ggplotnim 2 | 3 | proc effPhotonMass(ne: float): float = 4 | ## returns the effective photon mass for a given electron number density 5 | const alpha = 1.0 / 137.0 6 | const me = 511e3 # 511 keV 7 | # note the 1.97e-7 cubed to account for the length scale in `ne` 8 | result = sqrt( pow(1.97e-7, 3) * 4 * PI * alpha * ne / me ) 9 | 10 | proc numDensity(c: float): float = 11 | ## converts a molar concentration in mol / m^3 to a number density 12 | const Z = 2 # number of electron in helium atom 13 | result = Z * 6.022e23 * c 14 | 15 | proc molarAmount(p, vol, temp: float): float = 16 | ## calculates the molar amount of gas given a certain pressure, 17 | ## volume and temperature 18 | ## the pressure is assumed in mbar 19 | const gasConstant = 8.314 # joule K^-1 mol^-1 20 | let pressure = p * 1e2 # pressure in Pa 21 | result = pressure * vol / (gasConstant * temp) 22 | 23 | proc babyIaxoEffMass(p: float): float = 24 | ## calculates the effective photon (and thus axion mass) for BabyIAXO given 25 | ## a certain helium pressure in the BabyIAXO magnet 26 | const vol = 10.0 * (PI * pow(0.3, 2)) # 10m length, bore radius 30 cm 27 | # UPDATE: IAXO will be run at 4.2 K instead of 1.7 K 28 | # const temp = 1.7 # assume 1.7 K same as CAST 29 | const temp = 4.2 30 | once: 31 | echo "BabyIAXO magnet volume is ", vol, " m^3" 32 | echo "BabyIAXO magnet temperature is ", temp, " K" 33 | let amountMol = molarAmount(p, vol, temp) # amount of gas in mol 34 | let numPerMol = numDensity(amountMol / vol) # number of electrons per m^3 35 | result = effPhotonMass(numPerMol) 36 | 37 | proc logspace(start, stop: float, num: int, base = 10.0): seq[float] = 38 | ## generates evenly spaced points between start and stop in log space 39 | let linear = linspace(start, stop, num) 40 | result = linear.mapIt(pow(base, it)) 41 | 42 | proc makePlot(pstart, pstop: float, fname: string) = 43 | let pressures = logspace(pstart, pstop, 1000) # 1000 values from 1e-5 mbar to 500 mbar 44 | let masses = pressures.mapIt(babyIaxoEffMass(it)) # corresponding masses 45 | # convert both seqs to a dataframe 46 | let df = toDf({"P / mbar" : pressures, "m_a / eV" : masses}) 47 | ggplot(df, aes("P / mbar", "m_a / eV")) + 48 | geom_line() + 49 | scale_x_log10() + 50 | scale_y_log10() + 51 | ggtitle("Sensitive axion mass in eV depending on helium pressure in mbar") + 52 | ggsave(fname) 53 | 54 | makePlot(-6.0, 2.0, "media/recipes/rAxionMassesLogLog.png") 55 | -------------------------------------------------------------------------------- /src/ggplotnim/ggplot_utils.nim: -------------------------------------------------------------------------------- 1 | import math, options 2 | import chroma, ginger 3 | 4 | # TODO: move elsewhere! 5 | func font*[T: SomeNumber]( 6 | size: T = 12.0, 7 | color = color(0.0, 0.0, 0.0), # color defined in chroma 8 | family = "sans-serif", 9 | alignKind = taCenter, 10 | bold = false, 11 | slant: FontSlant = fsNormal # defined in ginger.types 12 | ): Font = 13 | result = Font(family: family, 14 | size: size, 15 | bold: bold, 16 | slant: slant, 17 | color: color, 18 | alignKind: alignKind) 19 | 20 | template unwrap*[T](opt: Option[T], raiseIfNil = true): untyped = 21 | var tmp: T 22 | if isSome opt: 23 | tmp = get opt 24 | elif raiseIfNil: 25 | raise newException(Exception, "Option " & $opt & " must exist") 26 | tmp 27 | 28 | proc calcRowsColumns*(rows, columns: int, nPlots: int): (int, int) = 29 | ## Calculates the desired rows and columns for # of `nPlots` given the user's 30 | ## input for `rows` and `columns`. 31 | ## - If no input is given, calculate the next possible rectangle of plots 32 | ## that favors columns over rows. 33 | ## - If either row or column is 0, sets this dimension to 1 34 | ## - If either row or column is -1, calculate square of nPlots for rows / cols 35 | ## - If both row and column is -1 or either -1 and the other 0, default back 36 | ## to the next possible square. 37 | if rows <= 0 and columns <= 0: 38 | # calc square of plots 39 | let sqPlt = sqrt(nPlots.float) 40 | result[1] = sqPlt.ceil.int 41 | result[0] = sqPlt.round.int 42 | elif rows == -1 and columns > 0: 43 | result[0] = (nPlots.float / columns.float).ceil.int 44 | result[1] = columns 45 | elif rows > 0 and columns == -1: 46 | result[0] = rows 47 | result[1] = (nPlots.float / rows.float).ceil.int 48 | elif rows == 0 and columns > 0: 49 | # 1 row, user desired # cols 50 | result = (1, columns) 51 | elif rows > 0 and columns == 0: 52 | # user desired # row, 1 col 53 | result = (rows, 1) 54 | else: 55 | result = (rows, columns) 56 | 57 | when isMainModule: 58 | # test the calculation of rows and columns 59 | doAssert calcRowsColumns(2, 0, 4) == (2, 1) 60 | doAssert calcRowsColumns(0, 2, 4) == (1, 2) 61 | doAssert calcRowsColumns(7, 3, 1) == (7, 3) 62 | doAssert calcRowsColumns(0, 0, 1) == (1, 1) 63 | doAssert calcRowsColumns(0, 0, 2) == (1, 2) 64 | doAssert calcRowsColumns(0, 0, 3) == (2, 2) 65 | doAssert calcRowsColumns(0, 0, 4) == (2, 2) 66 | doAssert calcRowsColumns(0, 0, 5) == (2, 3) 67 | doAssert calcRowsColumns(0, 0, 6) == (2, 3) 68 | doAssert calcRowsColumns(0, 0, 7) == (3, 3) 69 | doAssert calcRowsColumns(0, 0, 8) == (3, 3) 70 | doAssert calcRowsColumns(0, 0, 9) == (3, 3) 71 | doAssert calcRowsColumns(-1, 2, 4) == (2, 2) 72 | doAssert calcRowsColumns(-1, 0, 4) == (2, 2) 73 | doAssert calcRowsColumns(2, -1, 4) == (2, 2) 74 | -------------------------------------------------------------------------------- /data/mass_attenuation_nist_data.txt: -------------------------------------------------------------------------------- 1 | # Energy mu/rho mu_en/rho 2 | # $\si{\mega\electronvolt}$ $\si{\cm\squared\per\gram}$ $\si{\cm\squared\per\gram}$ 3 | 1.00000E-03 6.084E+01 6.045E+01 4 | 1.50000E-03 1.676E+01 1.638E+01 5 | 2.00000E-03 6.863E+00 6.503E+00 6 | 3.00000E-03 2.007E+00 1.681E+00 7 | 4.00000E-03 9.329E-01 6.379E-01 8 | 5.00000E-03 5.766E-01 3.061E-01 9 | 6.00000E-03 4.195E-01 1.671E-01 10 | 8.00000E-03 2.933E-01 6.446E-02 11 | 1.00000E-02 2.476E-01 3.260E-02 12 | 1.50000E-02 2.092E-01 1.246E-02 13 | 2.00000E-02 1.960E-01 9.410E-03 14 | 3.00000E-02 1.838E-01 1.003E-02 15 | 4.00000E-02 1.763E-01 1.190E-02 16 | 5.00000E-02 1.703E-01 1.375E-02 17 | 6.00000E-02 1.651E-01 1.544E-02 18 | 8.00000E-02 1.562E-01 1.826E-02 19 | 1.00000E-01 1.486E-01 2.047E-02 20 | 1.50000E-01 1.336E-01 2.424E-02 21 | 2.00000E-01 1.224E-01 2.647E-02 22 | 3.00000E-01 1.064E-01 2.868E-02 23 | 4.00000E-01 9.535E-02 2.951E-02 24 | 5.00000E-01 8.707E-02 2.971E-02 25 | 6.00000E-01 8.054E-02 2.959E-02 26 | 8.00000E-01 7.076E-02 2.890E-02 27 | 1.00000E+00 6.362E-02 2.797E-02 28 | 1.25000E+00 5.688E-02 2.674E-02 29 | 1.50000E+00 5.173E-02 2.555E-02 30 | 2.00000E+00 4.422E-02 2.343E-02 31 | 3.00000E+00 3.503E-02 2.019E-02 32 | 4.00000E+00 2.949E-02 1.790E-02 33 | 5.00000E+00 2.577E-02 1.622E-02 34 | 6.00000E+00 2.307E-02 1.493E-02 35 | 8.00000E+00 1.940E-02 1.308E-02 36 | 1.00000E+01 1.703E-02 1.183E-02 37 | 1.50000E+01 1.363E-02 9.948E-03 38 | 2.00000E+01 1.183E-02 8.914E-03 39 | -------------------------------------------------------------------------------- /recipes/rPeriodicTable.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, sequtils, seqmath, strutils 2 | 3 | ## 4 | ## This is a straight up adaptation from the genius `plotnine` example 5 | ## here: 6 | ## https://plotnine.readthedocs.io/en/stable/generated/plotnine.geoms.geom_tile.html 7 | ## 8 | 9 | var elements = readCsv("data/elements.csv") 10 | .mutate(f{Value -> int: "group" ~ ( 11 | if `group` == "-": -1 12 | else: `group`.toInt)}) 13 | echo elements.pretty(5) 14 | 15 | # split the lanthanides and actinides from the rest 16 | var top = elements.filter(f{`group` != -1}) 17 | .rename(f{"x" <- "group"}, 18 | f{"y" <- "period"}) 19 | var bottom = elements.filter(f{`group` == -1}) 20 | echo top["x"] 21 | echo top["y"] 22 | 23 | const nrows = 2 24 | const hshift = 3.5 25 | const vshift = 3.0 26 | bottom["x"] = toColumn cycle(arange(0, bottom.len div nrows), nrows).mapIt(it.float + hshift) 27 | bottom = bottom.mutate(f{"y" ~ `period` + vshift}) 28 | const tile_width = 0.95 29 | const tile_height = 0.95 30 | 31 | # replace `elements` by stacked top and bottom 32 | elements = bind_rows([top, bottom]) 33 | 34 | let splitDf = toDf({ 35 | "y": @[6, 7], 36 | "metal": @["lanthanoid", "actinoid"] 37 | }) 38 | 39 | func cycle[T](s: openArray[T]; nums: seq[int]): seq[T] = 40 | result = newSeq[T](nums.foldl(a + b)) 41 | var idx = 0 42 | for i in 0 ..< nums.len: 43 | for j in 0 ..< nums[i]: 44 | result[idx] = s[i] 45 | inc idx 46 | # finally define rows and cols 47 | let groupdf = toDf({ 48 | "group": arange(1, 19), 49 | "y": cycle(@[1, 2, 4, 2, 1], @[1, 1, 10, 5, 1])}) 50 | let periodDf = toDf({ 51 | "period": arange(1, 8), 52 | "x": cycle(@[0.5], @[7])}) 53 | 54 | ggplot(elements, aes("x", "y", fill = "metal")) + 55 | geom_tile(aes = aes(width = tileWidth, 56 | height = tileHeight)) + 57 | geom_tile(data = splitDf, 58 | aes = aes(x = 3 - tileWidth/4.0 + 0.25, 59 | width = tileWidth / 2.0, 60 | height = tileHeight)) + 61 | scale_y_continuous() + 62 | geom_text(aes(x = f{`x` + 0.15}, 63 | y = f{`y` + 0.15}, 64 | text = "atomic number"), 65 | font = font(6.0)) + 66 | geom_text(aes(x = f{`x` + 0.5}, 67 | y = f{`y` + 0.4}, 68 | text = "symbol"), 69 | font = font(9.0)) + 70 | geom_text(aes(x = f{`x` + 0.5}, 71 | y = f{`y` + 0.6}, 72 | text = "name"), 73 | font = font(4.5)) + 74 | geom_text(aes(x = f{`x` + 0.5}, 75 | y = f{`y` + 0.8}, 76 | text = "atomic mass"), 77 | font = font(4.5)) + 78 | geom_text(data = groupdf, 79 | aes = aes(x = f{`group` + 0.5}, 80 | y = f{`y` - 0.2}, 81 | text = "group"), 82 | font = font(9.0, color = color(0.5, 0.5, 0.5))) + 83 | geom_text(data = periodDf, 84 | aes = aes(x = f{`x` + 0.3}, 85 | y = f{`period` + 0.5}, 86 | text = "period"), 87 | font = font(9.0, color = color(0.5, 0.5, 0.5))) + 88 | legendPosition(0.82, 0.1) + 89 | theme_void() + 90 | margin(right = 5, bottom = 2) + # adjust margin as `theme_void` implies tight margins 91 | scale_y_reverse() + 92 | scale_x_continuous() + 93 | ggsave("media/recipes/rPeriodicTable.png", 94 | width = 1000, 95 | height = 500) 96 | -------------------------------------------------------------------------------- /src/ggplotnim/theme_toml.nim: -------------------------------------------------------------------------------- 1 | import std / [strutils, sugar, options, typetraits] 2 | import parsetoml 3 | import ./ggplot_types 4 | from ginger import Font, FontSlant, TextAlignKind, Quantity, quant, Color, ukCentimeter 5 | 6 | proc assignKey(f: var Font, argTup: seq[string]) = 7 | ## Assigns the key (arg 0) to the correct field using value (arg 1) 8 | doAssert argTup.len == 2 9 | let key = argTup[0].strip() 10 | let val = argTup[1].strip() 11 | case key 12 | of "bold": f.bold = parseBool val 13 | of "family": f.family = val 14 | of "size": f.size = parseFloat val 15 | of "slant": f.slant = parseEnum[FontSlant](val) 16 | of "color": f.color = toOptColor(val).get 17 | of "alignKind": f.alignKind = parseEnum[TextAlignKind](val) 18 | 19 | proc assignIdx(f: var Font, arg: string, idx: int) = 20 | ## Assigns the value of `arg` to the field index `idx` of the proc `ggplot_utils/font` 21 | ## 22 | ## size: T = 12.0, 23 | ## color = color(0.0, 0.0, 0.0), # color defined in chroma 24 | ## family = "sans-serif", 25 | ## alignKind = taCenter, 26 | ## bold = false, 27 | ## slant: FontSlant = fsNormal # defined in ginger.types 28 | case idx 29 | of 0: f.size = parseFloat arg 30 | of 1: f.color = toOptColor(arg).get 31 | of 2: f.family = arg 32 | of 3: f.alignKind = parseEnum[TextAlignKind](arg) 33 | of 4: f.bold = parseBool arg 34 | of 5: f.slant = parseEnum[FontSlant](arg) 35 | else: doAssert false, "Invalid index " & $idx & "!" 36 | 37 | proc parseFont(fnt: string): Font = 38 | if not fnt.startsWith("font("): 39 | raise newException(ValueError, "Invalid font description in the TOML file. Field is " & 40 | fnt & ", but should be of type `font()`.") 41 | result.color = toOptColor("black").get 42 | let args = fnt.dup(removePrefix("font(")).dup(removeSuffix(")")).split(",") 43 | var argIdx = 0 44 | for arg in args: 45 | if "=" in arg: 46 | # has a key 47 | result.assignKey(arg.split("=")) 48 | else: 49 | result.assignIdx(arg.strip(), argIdx) 50 | inc argIdx 51 | 52 | template getInnerOptionType(t: typed): untyped = 53 | ## Given an element of `Option[T]` 'returns' `T` 54 | genericParams(typeof(t)).get(0) 55 | 56 | proc parseAnyEnum[T](s: string, dtype: typedesc[T]): T = 57 | result = parseEnum[T](s) 58 | 59 | proc parseTheme*(fname: string, tab = "Theme"): Theme = 60 | ## Parses a `Theme` to use for the plot from the given TOML file. 61 | ## 62 | ## This is done by attempting to walk all theme fields of the `Theme` Nim object 63 | ## and checking for fields of the `tab` table in the TOML file for fields of the 64 | ## same name. If any exists, sets them in the return. 65 | let config = parseToml.parseFile(fname) 66 | for field, val in fieldPairs(result): 67 | if field in config[tab]: 68 | let cVal {.used.} = config[tab][field] 69 | when typeof(val) is Option[SomeNumber]: 70 | val = some(getInnerOptionType(val)(cVal.getFloat)) 71 | elif typeof(val) is Option[Font]: 72 | let fontStr = cVal.getStr 73 | # now parse the font 74 | val = some(parseFont(fontStr)) 75 | elif typeof(val) is Option[enum]: 76 | val = some(parseAnyEnum(cVal.getStr, getInnerOptionType(val))) 77 | elif typeof(val) is Option[bool]: 78 | val = some(cVal.getBool) 79 | elif typeof(val) is Option[Color]: 80 | val = toOptColor(cVal.getStr) 81 | elif typeof(val) is Option[Quantity]: ## XXX: for now assume centimeters 82 | val = some(quant(cVal.getFloat, ukCentimeter)) 83 | else: 84 | echo "[WARNING] Field ", field, " of type ", typeof(val), " is currently still unsupported!" 85 | -------------------------------------------------------------------------------- /recipes/recipeFiles.nim: -------------------------------------------------------------------------------- 1 | import os, strutils 2 | import shell 3 | 4 | const RecipeFiles* = @["rStackedMpgHistogram", 5 | "rNewtonAcceleration", 6 | "rMpgStackedPointPlot", 7 | "rLinePlotSize", 8 | "rMpgHistoBinWidth", 9 | "rMpgContinuousColorPoints", 10 | "rAxionMassVsDensity", 11 | "rMpgHistoNumBins", 12 | "rMpgHistoCustomBreaks", 13 | "rMpgCustomColorPoint", 14 | "rMpgHistoPlusPoints", 15 | "rSimpleLinePlot", 16 | "rMpgSimpleBarPlot", 17 | "rTwoSensorsBadStyle", 18 | "rTwoSensorsGoodStyle", 19 | "rPrebinnedHisto", 20 | "rMassAttenuationFunction", 21 | "rAxionMassesLogLog", 22 | "rStackedMpgFreqpoly", 23 | "rMpgStackedBarPlot", 24 | "rBarPlotRotatedLabels", 25 | "rBarPlotCompStats", 26 | "rCustomAnnotations", 27 | "rMpgDiscreteXScale", 28 | "rDiscreteXLine", 29 | "rEnlargeXRange", 30 | "rLimitXRange", 31 | "rCreateMarginBuffer", 32 | "rHighlightMinMax", 33 | "rFormulaAesthetic", 34 | "rErrorBar", 35 | "rDiscreteYAxis", 36 | "rBothDiscreteAxes", 37 | "rFreqPolyWithAlpha", 38 | "rMultipleLegends", 39 | "rSimpleTile", 40 | "rSimpleGeomText", 41 | "rClassifiedGeomText", 42 | "rAnnotateUsingGeomText", 43 | "rAnnotateMaxValues", 44 | "rPeriodicTable", 45 | "rAutoColoredNeuralSpikes", 46 | "rCustomColoredNeuralSpikes", 47 | "rNegativeBarPlot", 48 | "rSimpleFacet", 49 | "rFacetTpa", 50 | "rFormatDatesPlot", 51 | "rFormatDecimalsPlot", 52 | "rWeightedHistogram", 53 | "rPointInPolygons", 54 | "rSimpleRaster", 55 | "rFacetRaster", 56 | "rCustomFill", 57 | "rCustomMargins", 58 | "rLongTitleMultiline", 59 | "rAnnotatedHeatmap", 60 | "rMultiSubplots", 61 | "rHistogramDensity", 62 | "rHistogramOutline", 63 | "rRidgeLineGauss", 64 | "rRidgeLineGaussBlack", 65 | "rJoyplot", 66 | "rGeomSmooth", 67 | "rLinearFit", 68 | "rScaleXDate", 69 | "rCustomBreaks", 70 | "rColormaps", 71 | "rCustomColormap" 72 | ] 73 | 74 | proc generateJsonFile*(f: string, toRun = false) = 75 | discard existsOrCreateDir("resources/recipes") 76 | let fname = "recipes" / f 77 | let jsonFile = fname & "_json.nim" 78 | 79 | # read the file 80 | let fcontent = readFile(fname & ".nim") 81 | # replace `ggsave` by `ggjson` and write file back 82 | const jsonImport = "from json import `%`\n" 83 | writeFile(jsonFile, jsonImport & fcontent.multiReplace(@[("ggsave(", "ggjson("), 84 | ("media/", "resources/")])) 85 | # run the tmp file to generate json 86 | if toRun: 87 | shell: 88 | nim c "-r" ($jsonFile) 89 | 90 | when isMainModule: 91 | for f in RecipeFiles: 92 | generateJsonFile(f) 93 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ggplotnim CI 2 | on: 3 | push: 4 | paths: 5 | - 'tests/**' 6 | - 'src/**' 7 | - 'ggplotnim.nimble' 8 | - '.github/workflows/ci.yml' 9 | branches: 10 | - 'master' 11 | pull_request: 12 | paths: 13 | - 'tests/**' 14 | - 'src/**' 15 | - 'ggplotnim.nimble' 16 | - '.github/workflows/ci.yml' 17 | 18 | jobs: 19 | build: 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | branch: [version-2-0, version-1-6, devel] 24 | target: [linux, macos, windows] 25 | include: 26 | - target: linux 27 | builder: ubuntu-latest 28 | - target: macos 29 | builder: macos-latest 30 | - target: windows 31 | builder: windows-latest 32 | name: '${{ matrix.target }} (${{ matrix.branch }})' 33 | runs-on: ${{ matrix.builder }} 34 | steps: 35 | - name: Checkout 36 | uses: actions/checkout@v2 37 | with: 38 | path: ggplotnim 39 | 40 | - name: Setup Nim 41 | uses: alaviss/setup-nim@0.1.1 42 | with: 43 | path: nim 44 | version: ${{ matrix.branch }} 45 | 46 | - name: Install dependencies (Ubuntu) 47 | if: ${{matrix.target == 'linux'}} 48 | run: | 49 | sudo apt-get update 50 | sudo apt-get install libcairo2 libcairo2-dev imagemagick \ 51 | libgtk-3-dev webkit2gtk-driver \ 52 | libwebkit2gtk-4.0 libwebkit2gtk-4.0-dev pandoc 53 | 54 | - name: Install dependencies (OSX) 55 | if: ${{matrix.target == 'macos'}} 56 | run: | 57 | brew install imagemagick cairo 58 | 59 | - name: Setup MSYS2 (Windows) 60 | if: ${{matrix.target == 'windows'}} 61 | uses: msys2/setup-msys2@v2 62 | with: 63 | path-type: inherit 64 | update: true 65 | install: base-devel git mingw-w64-x86_64-toolchain 66 | 67 | - name: Install dependencies (Windows) 68 | if: ${{matrix.target == 'windows'}} 69 | shell: msys2 {0} 70 | run: | 71 | pacman -Syu --noconfirm 72 | pacman -S --needed --noconfirm mingw-w64-x86_64-cairo 73 | pacman -S --needed --noconfirm mingw-w64-x86_64-lapack 74 | 75 | - name: Setup nimble & deps 76 | shell: bash 77 | run: | 78 | # limit our stack size to avoid regressing after PR #36 w/o noticing 79 | ulimit -s 1024 80 | cd ggplotnim 81 | nimble refresh -y 82 | nimble install ntangle -y 83 | nimble install -y 84 | 85 | - name: Run tests (Linux & Mac) 86 | if: ${{matrix.target != 'windows'}} 87 | shell: bash 88 | run: | 89 | cd ggplotnim 90 | nimble -y fullTest 91 | nimble -y testCI 92 | 93 | - name: Run tests (Windows) 94 | if: ${{matrix.target == 'windows'}} 95 | shell: msys2 {0} 96 | run: | 97 | cd ggplotnim 98 | nimble -y fullTest 99 | nimble -y testCI 100 | 101 | - name: Build docs 102 | if: > 103 | github.event_name == 'push' && github.ref == 'refs/heads/master' && 104 | matrix.target == 'linux' && matrix.branch == 'devel' 105 | shell: bash 106 | run: | 107 | cd ggplotnim 108 | # **HAVE** to call `develop`, cuz we're getting screwed by 109 | # logic otherwise 110 | nimble develop -y 111 | nimble gen_docs 112 | # TODO: fix this, need to iterate over all files, do similar to arraymancer docs 113 | # Ignore failures for older Nim 114 | cp docs/{the,}index.html || true 115 | 116 | - name: Publish docs 117 | if: > 118 | github.event_name == 'push' && github.ref == 'refs/heads/master' && 119 | matrix.target == 'linux' && matrix.branch == 'devel' 120 | uses: crazy-max/ghaction-github-pages@v1 121 | with: 122 | build_dir: ggplotnim/docs 123 | env: 124 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 125 | -------------------------------------------------------------------------------- /recipes/rAxionMassVsDensity.nim: -------------------------------------------------------------------------------- 1 | import sequtils, seqmath, ggplotnim 2 | 3 | proc logspace(start, stop: float, num: int, base = 10.0): seq[float] = 4 | ## generates evenly spaced points between start and stop in log space 5 | let linear = linspace(start, stop, num) 6 | result = linear.mapIt(pow(base, it)) 7 | 8 | proc density(p: float, temp = 4.2): float = 9 | ## returns the density of the gas for the given pressure. 10 | ## The pressure is assumed in `mbar` and the temperature (in `K`). 11 | ## The default temperature corresponds to BabyIAXO aim. 12 | ## Returns the density in `g / cm^3` 13 | const gasConstant = 8.314 # joule K^-1 mol^-1 14 | const M = 4.002602 # g / mol 15 | let pressure = p * 1e2 # pressure in Pa 16 | # factor 1000 for conversion of M in g / mol to kg / mol 17 | result = pressure * M / (gasConstant * temp * 1000.0) 18 | # convert the result to g / cm^3 for use with mass attenuations 19 | result = result / 1000.0 20 | 21 | proc numDensity(c: float): float = 22 | ## converts a molar concentration in mol / m^3 to a number density 23 | const Z = 2 # number of electron in helium atom 24 | result = Z * 6.022e23 * c 25 | 26 | proc effPhotonMass(ne: float): float = 27 | ## returns the effective photon mass for a given electron number density 28 | const alpha = 1.0 / 137.0 29 | const me = 511e3 # 511 keV 30 | # note the 1.97e-7 cubed to account for the length scale in `ne` 31 | result = sqrt( pow(1.97e-7, 3) * 4 * PI * alpha * ne / me ) 32 | 33 | proc pressureGivenEffPhotonMass(m_gamma: float, T = 4.2): float = 34 | ## calculates the inverse of `babyIaxoEffPhotonMass`, i.e. the pressure 35 | ## from a given effective photon mass in BabyIAXO 36 | result = m_gamma * m_gamma * T / 0.01988 37 | 38 | proc molarAmount(p, vol, temp: float): float = 39 | ## calculates the molar amount of gas given a certain pressure, 40 | ## volume and temperature 41 | ## the pressure is assumed in mbar 42 | const gasConstant = 8.314 # joule K^-1 mol^-1 43 | let pressure = p * 1e2 # pressure in Pa 44 | result = pressure * vol / (gasConstant * temp) 45 | 46 | proc babyIaxoEffMass(p: float): float = 47 | ## calculates the effective photon (and thus axion mass) for BabyIAXO given 48 | ## a certain helium pressure in the BabyIAXO magnet 49 | const vol = 10.0 * (PI * pow(0.3, 2)) # 10m length, bore radius 30 cm 50 | const temp = 4.2 51 | let amountMol = molarAmount(p, vol, temp) # amount of gas in mol 52 | let numPerMol = numDensity(amountMol / vol) # number of electrons per m^3 53 | result = effPhotonMass(numPerMol) 54 | 55 | proc vacuumMassLimit(energy: float, magnetLength = 10.0): float = 56 | ## given an axion energy in keV, calculate the limit of coherence 57 | ## for the vacuum case in BabyIAXO 58 | let babyIaxoLen = magnetLength / 1.97e-7 # length in "eV" 59 | result = sqrt(PI * 2 * energy * 1e3 / babyIaxoLen) # convert energy to eV 60 | 61 | const babyIaxoVacuumMassLimit = vacuumMassLimit(4.2) 62 | proc m_a_vs_density(pstart, pstop: float) = 63 | let pressures = logspace(pstart.log10, pstop.log10, 1000) 64 | let densities = pressures.mapIt(density(it, 4.2)) 65 | let masses = pressures.mapIt(babyIaxoEffMass(it)) # corresponding masses 66 | # convert both seqs to a dataframe 67 | let ref1Bar = density(1000, 293.15) 68 | let df1bar = toDf({"ρ / g/cm^3" : @[ref1Bar, ref1Bar], "m_a / eV" : @[1e-2, 1.0]}) 69 | let ref3Bar = density(3000, 293.15) 70 | let df3bar = toDf({"ρ / g/cm^3" : @[ref3Bar, ref3Bar], "m_a / eV" : @[1e-2, 1.0]}) 71 | let refVacLimit = density(pressureGivenEffPhotonMass(babyIaxoVacuumMassLimit)) 72 | let dfVacLimit = toDf({"ρ / g/cm^3" : @[refVacLimit, refVacLimit], "m_a / eV" : @[1e-2, 1.0]}) 73 | let df = toDf({"ρ / g/cm^3" : densities, "m_a / eV" : masses}) 74 | let dfComb = bind_rows([("ma vs ρ", df), 75 | ("1 bar @ 293 K", df1bar), 76 | ("3 bar @ 293 K", df3bar), 77 | ("Vacuum limit", dfVacLimit)], 78 | id = "Legend") 79 | ggplot(dfComb, aes("ρ / g/cm^3", "m_a / eV", color = "Legend")) + 80 | geom_line() + 81 | scale_x_log10() + 82 | scale_y_log10() + 83 | ggtitle("Sensitive axion mass in eV depending on helium density in g / cm^3") + 84 | ggsave("media/recipes/rAxionMassVsDensity.png") 85 | 86 | m_a_vs_density(pressureGivenEffPhotonMass(babyIaxoVacuumMassLimit) * 0.9, 87 | pressureGivenEffPhotonMass(0.4521) * 1.1) 88 | -------------------------------------------------------------------------------- /src/ggplotnim/ggplot_theme.nim: -------------------------------------------------------------------------------- 1 | import options, sequtils 2 | import ginger 3 | import ggplot_types, ggplot_scales 4 | import datamancer 5 | 6 | proc getPlotBackground*(theme: Theme): Style = 7 | ## returns a suitable style (or applies default) for the background of 8 | ## the plot area 9 | result = Style(color: color(0.0, 0.0, 0.0, 0.0)) 10 | if theme.plotBackgroundColor.isSome: 11 | result.fillColor = theme.plotBackgroundColor.unsafeGet 12 | else: 13 | # default color: `grey92` 14 | result.fillColor = grey92 15 | 16 | proc getCanvasBackground*(theme: Theme): Style = 17 | ## returns a suitable style (or applies default) for the background color of 18 | ## the whole plot canvas. By default it is transparent 19 | result = Style(color: transparent) 20 | if theme.canvasColor.isSome: 21 | result.fillColor = theme.canvasColor.unsafeGet 22 | else: 23 | # default background: white 24 | result.fillColor = white 25 | 26 | proc getGridLineStyle*(theme: Theme): Style = 27 | ## Extracts the fields of `theme` that are set for grid lines and applies 28 | ## defaults else. 29 | result = Style(lineWidth: 1.0, 30 | color: white, 31 | lineType: ltSolid) 32 | if theme.gridLineColor.isSome: 33 | result.color = theme.gridLineColor.unsafeGet 34 | if theme.gridLineWidth.isSome: 35 | result.lineWidth = theme.gridLineWidth.unsafeGet 36 | 37 | proc getMinorGridLineStyle*(majorStyle: Style, theme: Theme): Style = 38 | ## Extracts the fields of `theme` that are set for minor grid lines and applies 39 | ## defaults else. 40 | result = Style(lineWidth: majorStyle.lineWidth / 2.0, 41 | color: majorStyle.color, 42 | lineType: ltSolid) 43 | if theme.minorGridLineWidth.isSome: 44 | result.lineWidth = theme.minorGridLineWidth.unsafeGet 45 | 46 | proc calculateMarginRange*(theme: Theme, scale: ginger.Scale, axKind: AxisKind): ginger.Scale = 47 | var margin: float 48 | case axKind 49 | of akX: margin = if theme.xMargin.isSome: theme.xMargin.unsafeGet else: 0.0 50 | of akY: margin = if theme.yMargin.isSome: theme.yMargin.unsafeGet else: 0.0 51 | let diff = scale.high - scale.low 52 | result = (low: scale.low - diff * margin, 53 | high: scale.high + diff * margin) 54 | 55 | func hasSecondary*(theme: Theme, axKind: AxisKind): bool = 56 | case axKind 57 | of akX: 58 | if theme.xLabelSecondary.isSome: 59 | result = true 60 | of akY: 61 | if theme.yLabelSecondary.isSome: 62 | result = true 63 | 64 | func labelName(filledScales: FilledScales, p: GgPlot, axKind: AxisKind): string = 65 | ## extracts the correct label for the given axis. 66 | ## First checks whether the theme sets a name, then checks the name of the 67 | ## x / y `Scale` and finally defaults to the column name. 68 | # doAssert p.aes.x.isSome, "x scale should exist?" 69 | case axKind 70 | of akX: 71 | let xScale = getXScale(filledScales) 72 | if xScale.name.len > 0: 73 | result = xScale.name 74 | else: 75 | result = $xScale.col 76 | of akY: 77 | let yScale = getYScale(filledScales) 78 | if yScale.name.len > 0: 79 | result = yScale.name 80 | elif yScale.col.name.len > 0: 81 | result = $yScale.col 82 | else: 83 | ## TODO: make this nicer by having a better approach to propagate 84 | ## the density information from geoms to here! 85 | result = if filledScales.geoms.anyIt(it.geom.statKind == stDensity or 86 | (it.geom.statKind == stBin and 87 | it.geom.density)): 88 | "density" 89 | else: 90 | "count" 91 | 92 | proc buildTheme*(filledScales: FilledScales, p: GgPlot): Theme = 93 | ## builds the final theme used for the plot. It takes the theme of the 94 | ## `GgPlot` object and fills in all missing fields as required from 95 | ## `filledScales` and `p`. 96 | result = p.theme 97 | if result.xLabel.isNone: 98 | result.xLabel = some(labelName(filledScales, p, akX)) 99 | if result.yLabel.isNone: 100 | result.yLabel = some(labelName(filledScales, p, akY)) 101 | if result.xLabelSecondary.isNone and filledScales.hasSecondary(akX): 102 | result.xLabelSecondary = some(filledScales.getSecondaryAxis(akX).name) 103 | if result.yLabelSecondary.isNone and filledScales.hasSecondary(akY): 104 | result.yLabelSecondary = some(filledScales.getSecondaryAxis(akY).name) 105 | 106 | # calculate `xMarginRange`, `yMarginRange` if any 107 | let xScale = if result.xRange.isSome: result.xRange.unsafeGet else: filledScales.xScale 108 | result.xMarginRange = result.calculateMarginRange(xScale, akX) 109 | let yScale = if result.yRange.isSome: result.yRange.unsafeGet else: filledScales.yScale 110 | result.yMarginRange = result.calculateMarginRange(yScale, akY) 111 | -------------------------------------------------------------------------------- /bookPlots/chapter2.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim 2 | 3 | proc ch2_3() = 4 | let mpg = toDf(readCsv("../data/mpg.csv")) 5 | 6 | # the code is basically the same as for R, but we have to hand 7 | # strings explicitly for the column names. Adding a template 8 | # taking `untyped` does not work, since the proc of the same 9 | # is called instead 10 | ggplot(mpg, aes(x = "displ", y = "hwy")) + 11 | geom_point() + 12 | ggsave("figs/2.3_1.pdf") 13 | 14 | # same plot, but not specifying arguments of `aes` 15 | ggplot(mpg, aes("displ", "hwy")) + 16 | geom_point() + 17 | ggsave("figs/2.3_2.pdf") 18 | 19 | # exercises plots 20 | # nonsense plot 21 | # TODO: broken, because we cannot deal with string data at the moment 22 | # Should create ticks for each possible labels in x / y and create a 23 | # scatter plot like that. X und Y scale thus become based on classes 24 | # tick numbers replaced by values! 25 | # ggplot(mpg, aes("model", "manufacturer")) + 26 | # geom_point() + 27 | # ggsave("figs/2.3_3.pdf") 28 | 29 | ggplot(mpg, aes("cty", "hwy")) + 30 | geom_point() + 31 | ggsave("figs/2.3_4.pdf") 32 | 33 | let diamonds = toDf(readCsv("../data/diamonds.csv")) 34 | let economics = toDf(readCsv("../data/economics.csv")) 35 | 36 | # works but ylabels are to close to the plot so that they are 37 | # on top of the tick values 38 | # also not as fast as it could be :/ 39 | ggplot(diamonds, aes("carat", "price")) + 40 | geom_point() + 41 | ggsave("figs/2.3_5.pdf") 42 | 43 | # broken, since we cannot parse dates. `geom_line` is now supported. 44 | # See R example. 45 | # Parsing dates is easy to add, once we 46 | # decide how to handle it. Convert to time in e.g. seconds and 47 | # calculate ticks based that? Probably best to work with native 48 | # dates using `times` module? Should be able to provide `<`, `<=` 49 | # and `==` (which should be all we need?) 50 | # also need a smart way to convert those dates back to strings for 51 | # labeling 52 | #ggplot(economics, aes("date", "unemploy")) + 53 | # geom_line() + 54 | # ggsave("figs/2.3_6.pdf") 55 | 56 | ggplot(mpg, aes("cty")) + 57 | geom_histogram() + 58 | ggsave("figs/2.3_7.pdf") 59 | 60 | proc ch2_4() = 61 | let mpg = toDf(readCsv("../data/mpg.csv")) 62 | # plots with different third dimension 63 | ggplot(mpg, aes(x = "displ", y = "hwy", color = "class")) + 64 | geom_point() + 65 | ggsave("figs/2.4_1.pdf") 66 | 67 | # broken, because `shape` is not yet supported yet. It's a dummy for the 68 | # `aes` proc, because we have not implemented enough different shapes 69 | # yet to support it 70 | #ggplot(mpg, aes(x = "displ", y = "hwy", shape = "drv")) + 71 | # geom_point() + 72 | # ggsave("figs/2.4_2.pdf") 73 | 74 | # `size` is still broken. Implementation is pretty easy though, we just 75 | # have to set the size instead of color for each data point based on 76 | # the continouos data scale given by the data of the given key. Should 77 | # probably define a range in of the size of the points e.g. 1.0 - 10.0 78 | # points or whatever and scale in that. Using Quantities that should be easy. 79 | # works now. 80 | ggplot(mpg, aes(x = "displ", y = "hwy", size = "cyl")) + 81 | geom_point() + 82 | ggsave("figs/2.4_3.pdf") 83 | 84 | # Classifying a geom by a non existant key in the data frame now works. 85 | # It's currently implemented by checking whether the key exists in the 86 | # data frame. If not, we add a dummy value with the value corresponding 87 | # to the key given. We could extend this to even support numerical values, 88 | # but then we have to change the `col` field of the `Scale` to be a `Value`. 89 | # For more complicated things, mutating the data frame beforehand is certainly 90 | # advisable though. 91 | # NOTE: This currently still only works for `geom_point`, since we simply 92 | # have not implemented `aes` arguments to the other geoms. That's an easy 93 | # change though. 94 | # NOTE2: currently broken again, due to change in how `geom_point` gets data 95 | # But, once solved will also work for other geoms! 96 | # NOTE3: geom_point is fixed again, also added `aes` to all geom procs. 97 | # Should work for all geoms now 98 | ggplot(mpg, aes(x = "displ", y = "hwy")) + 99 | geom_point(aes(color = "blue")) + 100 | # geom_line(aes(color = "blue")) + # also works 101 | ggsave("figs/2.4_4.pdf") 102 | 103 | # works now 104 | ggplot(mpg, aes(x = "displ", y = "hwy")) + 105 | geom_point(color = some(parseHex("0000FF"))) + 106 | ggsave("figs/2.4_5.pdf") 107 | 108 | proc ch2_5() = 109 | let mpg = toDf(readCsv("../data/mpg.csv")) 110 | 111 | ggplot(mpg, aes("displ", "hwy")) + 112 | geom_point() + 113 | facet_wrap(~ class) + 114 | ggsave("figs/2.5_1.pdf") 115 | 116 | ggplot(mpg, aes("displ", "hwy")) + 117 | geom_point(aes(color = "manufacturer")) + 118 | facet_wrap(~ class) + 119 | ggsave("figs/2.5_2.pdf") 120 | 121 | 122 | when isMainModule: 123 | ch2_3() 124 | ch2_4() 125 | ch2_5() 126 | -------------------------------------------------------------------------------- /ggplotnim.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.7.4" 4 | author = "Sebastian Schmidt" 5 | description = "A port of ggplot2 for Nim" 6 | license = "MIT" 7 | srcDir = "src" 8 | 9 | # Dependencies 10 | 11 | requires "nim >= 1.0.0" 12 | requires "https://github.com/Vindaar/seqmath >= 0.1.16" 13 | requires "ginger == 0.6.1" 14 | requires "datamancer >= 0.5.1" 15 | requires "arraymancer >= 0.7.22" 16 | requires "shell >= 0.4.3" 17 | requires "webview" 18 | requires "parsetoml" 19 | requires "https://github.com/SciNim/scinim >= 0.1.0" 20 | 21 | task testCI, "Run standard tests w/o cairo dependency": 22 | # This runs all tests suitable for a CI environment, which does not provide 23 | # cairo. Most tests are independent of cairo anyways 24 | when defined(windows): 25 | exec "nim c -d:noCairo -d:lapack=liblapack -r tests/tests.nim" 26 | exec "nim c -d:noCairo -d:lapack=liblapack -r tests/tvega.nim" 27 | exec "nim c -d:noCairo -d:lapack=liblapack -r tests/test_issue2.nim" 28 | else: 29 | exec "nim c -d:noCairo -r tests/tests.nim" 30 | exec "nim c -d:noCairo -r tests/tvega.nim" 31 | exec "nim c -d:noCairo -r tests/test_issue2.nim" 32 | 33 | task test, "Run standard tests": 34 | when defined(windows): 35 | exec "nim c -r -d:lapack=liblapack tests/tests.nim" 36 | exec "nim c -r -d:lapack=liblapack tests/tvega.nim" 37 | exec "nim c -r -d:lapack=liblapack tests/test_issue2.nim" 38 | else: 39 | exec "nim c -r tests/tests.nim" 40 | exec "nim c -r tests/tvega.nim" 41 | exec "nim c -r tests/test_issue2.nim" 42 | 43 | task fulltest, "Run all tests, including recipe comparison (requires ntangle)": 44 | when defined(windows): 45 | exec "nim c -r -d:lapack=liblapack tests/tests.nim" 46 | exec "nim c -r -d:lapack=liblapack tests/test_issue2.nim" 47 | exec "nim c -r -d:lapack=liblapack tests/tvega.nim" 48 | exec "nim c -r -d:lapack=liblapack tests/tCompareRecipes.nim" 49 | else: 50 | exec "nim c -r tests/tests.nim" 51 | exec "nim c -r tests/test_issue2.nim" 52 | exec "nim c -r tests/tvega.nim" 53 | exec "nim c -r tests/tCompareRecipes.nim" 54 | 55 | import os, strutils, strformat 56 | const 57 | pkgName = "ggplotnim" 58 | orgFile = "docs" / (pkgName & ".org") 59 | rstFile = "docs" / (pkgName & ".rst") 60 | rstFileAuto = "docs" / (pkgName & "_autogen.rst") 61 | 62 | proc basename(f: string): string = 63 | let (dir, name, ext) = f.splitFile 64 | result = name 65 | 66 | proc removePrefix(f, prefix: string): string = 67 | result = f 68 | result.removePrefix(prefix) 69 | 70 | template canImport(x: untyped): untyped = 71 | compiles: 72 | import x 73 | 74 | when canImport(docs / docs): 75 | # can define the `gen_docs` task (docs already imported now) 76 | # this is to hack around weird nimble + nimscript behavior. 77 | # when overwriting an install nimble will try to parse the generated 78 | # nimscript file and for some reason then it won't be able to import 79 | # the module (even if it's put into `src/`). 80 | task gen_docs, "Generate ggplotnim documentation": 81 | # build the actual docs and the index 82 | exec "pandoc " & orgFile & " -o " & rstFile 83 | buildDocs( 84 | "src/", "docs/", 85 | defaultFlags = "--hints:off --warnings:off" 86 | ) 87 | 88 | proc runRecipes() = 89 | when defined(windows): 90 | # depend on existing `.nim` files in repo on windows then.. 91 | exec "nim c -r -d:nimLegacyRandomInitRand -d:lapack=liblapack recipes/allRecipes.nim" 92 | else: 93 | exec "ntangle recipes.org" 94 | exec "nim c -r -d:nimLegacyRandomInitRand recipes/allRecipes.nim" 95 | 96 | task recipes, "Generate and run all recipes": 97 | runRecipes() 98 | 99 | task recipesJson, "Generate and run all recipes with JSON output": 100 | when defined(windows): 101 | # depend on existing `.nim` files in repo on windows then.. 102 | exec "nim c -r -d:lapack=liblapack recipes/recipeFiles.nim" # to generate the `_json.nim` files 103 | exec "nim c -r -d:lapack=liblapack -d:nimLegacyRandomInitRand recipes/allRecipesJson.nim" 104 | else: 105 | exec "ntangle recipes.org" 106 | exec "nim c -r recipes/recipeFiles.nim" # to generate the `_json.nim` files 107 | exec "nim c -r -d:nimLegacyRandomInitRand recipes/allRecipesJson.nim" 108 | 109 | task generateJson, "Generate all `_json.nim` recipe files that output JSON instead of a plot": 110 | exec "nim c -r recipes/runRecipes.nim --json" 111 | 112 | task recipesPlots, "Generate the PNGs from all recipes": 113 | exec """for f in media/recipes/r*.pdf; do inkscape $f --export-png="${f/.pdf/.png}"; done""" 114 | 115 | # Run the following command to generate everything required to possibly make CI pass, i.e. 116 | # to update all files. 117 | # After running these, to update all files required for the CI, you need to copy over 118 | # all pngs from `media/recipes/` to `media/expected/` and all JSON files from 119 | # `resources/recipes/` to `resources/expected/`. 120 | # This copy step is *not* automated, because it is important to verify the resulting 121 | # changes are what you intend with your changes! 122 | task generateAll, "Generate all output files (requires lualatex, inkscape)": 123 | exec "ntangle recipes.org" # generate recipes 124 | runRecipes() # run the regular recipes (produces new files in `media/recipes/` 125 | exec "nim c -r recipes/recipeFiles.nim" # to generate the `_json.nim` files 126 | exec "nim c -r -d:nimLegacyRandomInitRand recipes/allRecipesJson.nim" # generate `.json` output files 127 | exec "nim c -r recipes/rTikZLandau.nim" # generate `.tex` file for Landau 128 | exec "lualatex --shell-escape media/recipes/rTikZLandau.tex" 129 | exec "inkscape --export-type=png --pdf-poppler rTikZLandau.pdf" 130 | exec "mv rTikZLandau.png media/recipes/rTikZLandau.png" 131 | exec "rm rTikZLandau.pdf rTikZLandau.aux rTikZLandau.log" 132 | -------------------------------------------------------------------------------- /data/msleep.csv: -------------------------------------------------------------------------------- 1 | name,genus,vore,order,conservation,sleep_total,sleep_rem,sleep_cycle,awake,brainwt,bodywt 2 | Cheetah,Acinonyx,carni,Carnivora,lc,12.1,NA,NA,11.9,NA,50 3 | Owl monkey,Aotus,omni,Primates,NA,17,1.8,NA,7,0.0155,0.48 4 | Mountain beaver,Aplodontia,herbi,Rodentia,nt,14.4,2.4,NA,9.6,NA,1.35 5 | Greater short-tailed shrew,Blarina,omni,Soricomorpha,lc,14.9,2.3,0.133333333,9.1,0.00029,0.019 6 | Cow,Bos,herbi,Artiodactyla,domesticated,4,0.7,0.666666667,20,0.423,600 7 | Three-toed sloth,Bradypus,herbi,Pilosa,NA,14.4,2.2,0.766666667,9.6,NA,3.85 8 | Northern fur seal,Callorhinus,carni,Carnivora,vu,8.7,1.4,0.383333333,15.3,NA,20.49 9 | Vesper mouse,Calomys,NA,Rodentia,NA,7,NA,NA,17,NA,0.045 10 | Dog,Canis,carni,Carnivora,domesticated,10.1,2.9,0.333333333,13.9,0.07,14 11 | Roe deer,Capreolus,herbi,Artiodactyla,lc,3,NA,NA,21,0.0982,14.8 12 | Goat,Capri,herbi,Artiodactyla,lc,5.3,0.6,NA,18.7,0.115,33.5 13 | Guinea pig,Cavis,herbi,Rodentia,domesticated,9.4,0.8,0.216666667,14.6,0.0055,0.728 14 | Grivet,Cercopithecus,omni,Primates,lc,10,0.7,NA,14,NA,4.75 15 | Chinchilla,Chinchilla,herbi,Rodentia,domesticated,12.5,1.5,0.116666667,11.5,0.0064,0.42 16 | Star-nosed mole,Condylura,omni,Soricomorpha,lc,10.3,2.2,NA,13.7,0.001,0.06 17 | African giant pouched rat,Cricetomys,omni,Rodentia,NA,8.3,2,NA,15.7,0.0066,1 18 | Lesser short-tailed shrew,Cryptotis,omni,Soricomorpha,lc,9.1,1.4,0.15,14.9,0.00014,0.005 19 | Long-nosed armadillo,Dasypus,carni,Cingulata,lc,17.4,3.1,0.383333333,6.6,0.0108,3.5 20 | Tree hyrax,Dendrohyrax,herbi,Hyracoidea,lc,5.3,0.5,NA,18.7,0.0123,2.95 21 | North American Opossum,Didelphis,omni,Didelphimorphia,lc,18,4.9,0.333333333,6,0.0063,1.7 22 | Asian elephant,Elephas,herbi,Proboscidea,en,3.9,NA,NA,20.1,4.603,2547 23 | Big brown bat,Eptesicus,insecti,Chiroptera,lc,19.7,3.9,0.116666667,4.3,3e-04,0.023 24 | Horse,Equus,herbi,Perissodactyla,domesticated,2.9,0.6,1,21.1,0.655,521 25 | Donkey,Equus,herbi,Perissodactyla,domesticated,3.1,0.4,NA,20.9,0.419,187 26 | European hedgehog,Erinaceus,omni,Erinaceomorpha,lc,10.1,3.5,0.283333333,13.9,0.0035,0.77 27 | Patas monkey,Erythrocebus,omni,Primates,lc,10.9,1.1,NA,13.1,0.115,10 28 | Western american chipmunk,Eutamias,herbi,Rodentia,NA,14.9,NA,NA,9.1,NA,0.071 29 | Domestic cat,Felis,carni,Carnivora,domesticated,12.5,3.2,0.416666667,11.5,0.0256,3.3 30 | Galago,Galago,omni,Primates,NA,9.8,1.1,0.55,14.2,0.005,0.2 31 | Giraffe,Giraffa,herbi,Artiodactyla,cd,1.9,0.4,NA,22.1,NA,899.995 32 | Pilot whale,Globicephalus,carni,Cetacea,cd,2.7,0.1,NA,21.35,NA,800 33 | Gray seal,Haliochoerus,carni,Carnivora,lc,6.2,1.5,NA,17.8,0.325,85 34 | Gray hyrax,Heterohyrax,herbi,Hyracoidea,lc,6.3,0.6,NA,17.7,0.01227,2.625 35 | Human,Homo,omni,Primates,NA,8,1.9,1.5,16,1.32,62 36 | Mongoose lemur,Lemur,herbi,Primates,vu,9.5,0.9,NA,14.5,NA,1.67 37 | African elephant,Loxodonta,herbi,Proboscidea,vu,3.3,NA,NA,20.7,5.712,6654 38 | Thick-tailed opposum,Lutreolina,carni,Didelphimorphia,lc,19.4,6.6,NA,4.6,NA,0.37 39 | Macaque,Macaca,omni,Primates,NA,10.1,1.2,0.75,13.9,0.179,6.8 40 | Mongolian gerbil,Meriones,herbi,Rodentia,lc,14.2,1.9,NA,9.8,NA,0.053 41 | Golden hamster,Mesocricetus,herbi,Rodentia,en,14.3,3.1,0.2,9.7,0.001,0.12 42 | Vole ,Microtus,herbi,Rodentia,NA,12.8,NA,NA,11.2,NA,0.035 43 | House mouse,Mus,herbi,Rodentia,nt,12.5,1.4,0.183333333,11.5,4e-04,0.022 44 | Little brown bat,Myotis,insecti,Chiroptera,NA,19.9,2,0.2,4.1,0.00025,0.01 45 | Round-tailed muskrat,Neofiber,herbi,Rodentia,nt,14.6,NA,NA,9.4,NA,0.266 46 | Slow loris,Nyctibeus,carni,Primates,NA,11,NA,NA,13,0.0125,1.4 47 | Degu,Octodon,herbi,Rodentia,lc,7.7,0.9,NA,16.3,NA,0.21 48 | Northern grasshopper mouse,Onychomys,carni,Rodentia,lc,14.5,NA,NA,9.5,NA,0.028 49 | Rabbit,Oryctolagus,herbi,Lagomorpha,domesticated,8.4,0.9,0.416666667,15.6,0.0121,2.5 50 | Sheep,Ovis,herbi,Artiodactyla,domesticated,3.8,0.6,NA,20.2,0.175,55.5 51 | Chimpanzee,Pan,omni,Primates,NA,9.7,1.4,1.416666667,14.3,0.44,52.2 52 | Tiger,Panthera,carni,Carnivora,en,15.8,NA,NA,8.2,NA,162.564 53 | Jaguar,Panthera,carni,Carnivora,nt,10.4,NA,NA,13.6,0.157,100 54 | Lion,Panthera,carni,Carnivora,vu,13.5,NA,NA,10.5,NA,161.499 55 | Baboon,Papio,omni,Primates,NA,9.4,1,0.666666667,14.6,0.18,25.235 56 | Desert hedgehog,Paraechinus,NA,Erinaceomorpha,lc,10.3,2.7,NA,13.7,0.0024,0.55 57 | Potto,Perodicticus,omni,Primates,lc,11,NA,NA,13,NA,1.1 58 | Deer mouse,Peromyscus,NA,Rodentia,NA,11.5,NA,NA,12.5,NA,0.021 59 | Phalanger,Phalanger,NA,Diprotodontia,NA,13.7,1.8,NA,10.3,0.0114,1.62 60 | Caspian seal,Phoca,carni,Carnivora,vu,3.5,0.4,NA,20.5,NA,86 61 | Common porpoise,Phocoena,carni,Cetacea,vu,5.6,NA,NA,18.45,NA,53.18 62 | Potoroo,Potorous,herbi,Diprotodontia,NA,11.1,1.5,NA,12.9,NA,1.1 63 | Giant armadillo,Priodontes,insecti,Cingulata,en,18.1,6.1,NA,5.9,0.081,60 64 | Rock hyrax,Procavia,NA,Hyracoidea,lc,5.4,0.5,NA,18.6,0.021,3.6 65 | Laboratory rat,Rattus,herbi,Rodentia,lc,13,2.4,0.183333333,11,0.0019,0.32 66 | African striped mouse,Rhabdomys,omni,Rodentia,NA,8.7,NA,NA,15.3,NA,0.044 67 | Squirrel monkey,Saimiri,omni,Primates,NA,9.6,1.4,NA,14.4,0.02,0.743 68 | Eastern american mole,Scalopus,insecti,Soricomorpha,lc,8.4,2.1,0.166666667,15.6,0.0012,0.075 69 | Cotton rat,Sigmodon,herbi,Rodentia,NA,11.3,1.1,0.15,12.7,0.00118,0.148 70 | Mole rat,Spalax,NA,Rodentia,NA,10.6,2.4,NA,13.4,0.003,0.122 71 | Arctic ground squirrel,Spermophilus,herbi,Rodentia,lc,16.6,NA,NA,7.4,0.0057,0.92 72 | Thirteen-lined ground squirrel,Spermophilus,herbi,Rodentia,lc,13.8,3.4,0.216666667,10.2,0.004,0.101 73 | Golden-mantled ground squirrel,Spermophilus,herbi,Rodentia,lc,15.9,3,NA,8.1,NA,0.205 74 | Musk shrew,Suncus,NA,Soricomorpha,NA,12.8,2,0.183333333,11.2,0.00033,0.048 75 | Pig,Sus,omni,Artiodactyla,domesticated,9.1,2.4,0.5,14.9,0.18,86.25 76 | Short-nosed echidna,Tachyglossus,insecti,Monotremata,NA,8.6,NA,NA,15.4,0.025,4.5 77 | Eastern american chipmunk,Tamias,herbi,Rodentia,NA,15.8,NA,NA,8.2,NA,0.112 78 | Brazilian tapir,Tapirus,herbi,Perissodactyla,vu,4.4,1,0.9,19.6,0.169,207.501 79 | Tenrec,Tenrec,omni,Afrosoricida,NA,15.6,2.3,NA,8.4,0.0026,0.9 80 | Tree shrew,Tupaia,omni,Scandentia,NA,8.9,2.6,0.233333333,15.1,0.0025,0.104 81 | Bottle-nosed dolphin,Tursiops,carni,Cetacea,NA,5.2,NA,NA,18.8,NA,173.33 82 | Genet,Genetta,carni,Carnivora,NA,6.3,1.3,NA,17.7,0.0175,2 83 | Arctic fox,Vulpes,carni,Carnivora,NA,12.5,NA,NA,11.5,0.0445,3.38 84 | Red fox,Vulpes,carni,Carnivora,NA,9.8,2.4,0.35,14.2,0.0504,4.23 85 | -------------------------------------------------------------------------------- /tests/tCompareRecipes.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, unittest, strutils, os, sequtils, osproc, shell, json, macros 2 | 3 | #[ 4 | This test simply builds all recipes, runs them and compares the final image of 5 | all recipes with the expected plots in `media/expected` 6 | 7 | NOTE: this test depends on imagemagick, since it converts the PNG files to PPM! 8 | ]# 9 | 10 | import ../recipes/recipeFiles 11 | 12 | proc echoFields(j1, j2: JsonNode) = 13 | doAssert j1.len >= j2.len 14 | for kx, vx in pairs(j1): 15 | echo "Is ", kx, ", with val: ", vx, " in j2? ", kx in j2 16 | if kx notin j2: 17 | echo "^^^^^^\n" 18 | 19 | template returnOnFalse(c1, c2: untyped, key = ""): untyped = 20 | result = c1 == c2 21 | if not result: 22 | echo "Didn't match ", astToStr(c1), " == ", astToStr(c2) 23 | echo "Was ", c1, " and ", c2 24 | if key.len > 0: 25 | echo "For keys: ", key 26 | return false 27 | 28 | proc compareJson*(j1, j2: JsonNode): bool = 29 | returnOnFalse(j1.kind, j2.kind) 30 | case j1.kind 31 | of JObject: 32 | returnOnFalse(j1.len, j2.len) 33 | for k, v in pairs(j1): 34 | when not defined(linux): 35 | if k == "txtPos" or k == "txtText": # txtText broken for multiline 36 | echo "INFO: Skipping key ", k, " due to cairo differences in text " & 37 | "printing on different platforms" 38 | continue 39 | returnOnFalse(k in j2, true) 40 | returnOnFalse(compareJson(v, j2[k]), true, k) 41 | of JFloat: 42 | when defined(linux): 43 | let cmpFloat = almostEqual(j1.getFloat, j2.getFloat, 1e-2) 44 | else: 45 | ## TODO: due to some cairo issue related to different platforms we get different 46 | ## positions on mac/windows. For now we just use a much larger epsilon. Need to 47 | ## investigate this difference once I have access to a machine running windows again. 48 | let cmpFloat = abs(j1.getFloat - j2.getFloat) < 0.1 # crude manual... 49 | if not cmpFloat: 50 | echo "Float compare failed: ", j1.getFloat, " <-> ", j2.getFloat 51 | returnOnFalse(cmpFloat, true) 52 | of JArray: 53 | returnOnFalse(j1.len, j2.len) 54 | for i in 0 ..< j1.len: 55 | returnOnFalse(compareJson(j1[i], j2[i]), true) 56 | else: 57 | returnOnFalse(j1, j2) 58 | 59 | suite "Compare recipe output": 60 | when not defined(windows): 61 | ## TODO: disable windows testing for the time being. Check the tests manually on 62 | ## a windows machine and then reactivate once fixed 63 | test "Compare recipe generated plots": 64 | # first run recipes to make sure we have current recipe plots in 65 | # media/recipes which we can compare with media/expected 66 | const path = getProjectPath().parentDir 67 | let runRecipes = shellVerbose: 68 | nimble recipes 69 | let toContinue = runRecipes[1] == 0 70 | check toContinue 71 | if not toContinue: 72 | quit("Could not run recipes successfully, quitting recipe comparison") 73 | 74 | ## NOTE: these files cannot be compared as image files, because they contain 75 | ## text on non white (transparent) background, which is turned black after conversion 76 | ## to `ppm`. The text rendering on the cairo library on travis is different than 77 | ## locally, so it fails. These files are only tested using JSON below. 78 | const FilesToSkip = @[""] 79 | 80 | proc convertRead(path, f: string): Tensor[int] = 81 | let pathF = path / $f & ".png" 82 | check fileExists(pathF) 83 | let (_, _, fext) = pathF.splitFile 84 | let args = "-set colorspace Gray -separate -average -compress none -resize 40%" 85 | when not defined(windows): 86 | let res = shellVerbose: 87 | convert ($pathF) ($args) ($pathF.replace(fext, ".ppm")) 88 | else: 89 | # there's some windows tool called convert, need to prepend `magick` 90 | let infile = &"\"{pathF}\"" 91 | let outf = $pathF.replace(fext, ".ppm") 92 | let outfile = &"\"{outf}\"" 93 | let res = shellVerbose: 94 | magick ($infile) ($args) ($outfile) 95 | result = readFile(pathF.replace(fext, ".ppm")).splitLines()[3 .. ^1].foldl(a & b, "") 96 | .strip.split.mapIt(it.parseInt).toTensor() 97 | template checkFiles(f1, f2, fname: untyped): untyped = 98 | # store in `comp` to avoid check obliterating our terminal with the diff 99 | let diff = (f1 -. f2).abs.sum 100 | const LinuxDiff = 0.01 101 | when defined(linux): 102 | let comp = diff.float / 256.0 < (f1.size.float * LinuxDiff) 103 | else: 104 | let comp = diff.float / 256.0 < (f1.size.float * 0.1) # less than 10% pixels different :/ 105 | echo "Tensor is long: ", expected.len, " and diff ", diff 106 | echo "Real diff ", diff.float / 256.0, " needs to be smaller ", f1.size.float * LinuxDiff 107 | check comp 108 | if not comp: 109 | echo "Comparison failed for file: ", fname, " difference is: ", diff 110 | 111 | var idx = 0 112 | for f in RecipeFiles: 113 | if f in FilesToSkip: continue 114 | let expected = convertRead(path / "media/expected", f) 115 | let isnow = convertRead(path / "media/recipes", f) 116 | check isnow.len == expected.len 117 | checkFiles(expected, isnow, f) 118 | 119 | when defined(linux): 120 | test "Compare recipe plots via JSON": 121 | ## first generate JSON from all recipe files by creating temporary 122 | ## recipe files, in which the `ggsave` call is replaced by `ggjson`, which 123 | ## simply dumps 124 | const path = getProjectPath().parentDir 125 | let runRecipesJson = shellVerbose: 126 | nimble recipesJson 127 | let toContinue = runRecipesJson[1] == 0 128 | check toContinue 129 | if not toContinue: 130 | quit("Could not run recipes for JSON successfully, quitting recipe comparison") 131 | 132 | const FilesToSkip = @["rMultiSubplots", "rColormaps"] # cannot autogenerate JSON, because it does not 133 | # use a `ggsave` call 134 | for f in RecipeFiles: 135 | if f in FilesToSkip: continue 136 | # compare generated json with expected json 137 | let resFile = parseFile "resources/recipes" / f & ".json" 138 | echo "Checking ", f & ".json" 139 | let expFile = parseFile(("resources/expected" / f & ".json").replace("recipes/", "expected/")) 140 | let cmpRes = compareJson(resFile, expFile) 141 | check cmpRes 142 | if not cmpRes: 143 | echo "Comparison of ", resFile, " with ", expFile, " failed." 144 | break 145 | 146 | ## NOTE: this is only safe against regressions of `ggplotnim`, because the 147 | ## JSON we compare is ``before`` being embedded into the final root viewport 148 | ## in ginger! It is simply the `Viewport` and all objects + children, as they 149 | ## are handed to ginger to be drawn. 150 | -------------------------------------------------------------------------------- /docs/docs.nim: -------------------------------------------------------------------------------- 1 | import macros, strformat, strutils, sequtils, sets, tables, algorithm 2 | 3 | from os import parentDir, getCurrentCompilerExe, DirSep, extractFilename, `/`, setCurrentDir 4 | 5 | # NOTE: 6 | # for some time on devel 1.3.x `paramCount` and `paramStr` had to be imported 7 | # os, because they were removed for nimscript. This was reverted in: 8 | # https://github.com/nim-lang/Nim/pull/14658 9 | # For `nimdoc` we still have to import those from `os`! 10 | when defined(nimdoc): 11 | from os import getCurrentDir, paramCount, paramStr 12 | 13 | #[ 14 | This file is a slightly modified version of the same file of `nimterop`: 15 | https://github.com/nimterop/nimterop/blob/master/nimterop/docs.nim 16 | ]# 17 | 18 | 19 | proc getNimRootDir(): string = 20 | #[ 21 | hack, but works 22 | alternatively (but more complex), use (from a nim file, not nims otherwise 23 | you get Error: ambiguous call; both system.fileExists): 24 | import "$nim/testament/lib/stdtest/specialpaths.nim" 25 | nimRootDir 26 | ]# 27 | getCurrentCompilerExe().parentDir.parentDir 28 | 29 | const 30 | DirSep = when defined(windows): '\\' else: '/' 31 | 32 | proc execAction(cmd: string): string = 33 | var 34 | ccmd = "" 35 | ret = 0 36 | when defined(Windows): 37 | ccmd = "cmd /c " & cmd 38 | elif defined(posix): 39 | ccmd = cmd 40 | else: 41 | doAssert false 42 | 43 | (result, ret) = gorgeEx(ccmd) 44 | doAssert ret == 0, "Command failed: " & $ret & "\ncmd: " & ccmd & "\nresult:\n" & result 45 | 46 | template genRemove(name: untyped): untyped = 47 | proc `name`(s, toRemove: string): string = 48 | result = s 49 | result.`name`(toRemove) 50 | genRemove(removePrefix) 51 | genRemove(removeSuffix) 52 | 53 | proc getFiles*(path: string): seq[string] = 54 | # Add files and dirs here, which should be skipped. 55 | #const excludeDirs = [] 56 | #let ExcludeDirSet = toSet(excludeDirs) 57 | #if path.extractFilename in ExcludeDirSet: return 58 | # The files below are not valid by themselves, they are only included 59 | # from other files 60 | const excludeFiles = [ "formula.nim" ] 61 | let ExcludeFileSet = toSet(excludeFiles) 62 | 63 | for file in listFiles(path): 64 | if file.endsWith(".nim") and file.extractFilename notin ExcludeFileSet: 65 | result.add file 66 | for dir in listDirs(path): 67 | result.add getFiles(dir) 68 | 69 | proc buildDocs*(path: string, docPath: string, 70 | defaultFlags = "", 71 | masterBranch = "master", 72 | defines: openArray[string] = @[]) = 73 | ## Generate docs for all nim files in `path` and output all HTML files to the 74 | ## `docPath` in a flattened form (subdirectories are removed). 75 | ## 76 | ## If duplicate filenames are detected, they will be printed at the end. 77 | ## 78 | ## WARNING: not in use! `baseDir` is the project path by default and `files` and `path` are relative 79 | ## to that directory. Set to "" if using absolute paths. 80 | ## 81 | ## `masterBranch` is the name of the default branch to which the docs should link 82 | ## when clicking the `Source` button below a procedure etc. 83 | ## 84 | ## `defines` is a list of `-d:xxx` define flags (the `xxx` part) that should be passed 85 | ## to `nim doc` so that `getHeader()` is invoked correctly. 86 | ## 87 | ## Use the `--publish` flag with nimble to publish docs contained in 88 | ## `path` to Github in the `gh-pages` branch. This requires the ghp-import 89 | ## package for Python: `pip install ghp-import` 90 | ## 91 | ## WARNING: `--publish` will destroy any existing content in this branch. 92 | ## 93 | ## NOTE: `buildDocs()` only works correctly on Windows with Nim 1.0+ since 94 | ## https://github.com/nim-lang/Nim/pull/11814 is required. 95 | ## 96 | ## 97 | const gitUrl = "https://github.com/Vindaar/ggplotnim" 98 | ## WARNING: this means `gen_docs` *only* works if you use `nimble develop` on 99 | ## the repository. Nimble cannot deal with ****. This is frustrating. Thanks. 100 | let baseDir = execAction("nimble path ggplotnim").parentDir & $DirSep 101 | when defined(windows) and (NimMajor, NimMinor, NimPatch) < (1, 0, 0): 102 | echo "buildDocs() unsupported on Windows for Nim < 1.0 - requires PR #11814" 103 | else: 104 | let 105 | docPath = baseDir & docPath 106 | path = baseDir & path 107 | defStr = block: 108 | var defStr = " " & defaultFlags 109 | for def in defines: 110 | defStr &= " -d:" & def 111 | defStr 112 | nim = getCurrentCompilerExe() 113 | 114 | # now we walk the whole `path` and build the documentation for each `.nim` file. 115 | # While doing that we flatten the directory structure for the generated HTML files. 116 | # `src/foo/bar/baz.nim` just becomes 117 | # `docPath/baz.html`. 118 | # This allows for all files to be in the `docPath` directory, which means each 119 | # file will be able to find the `dochack.js` file, which will be put into 120 | # the `docPath` directory, too (the inclusion of the `dochack.js` is done statically 121 | # via our generated nimdoc.cfg file and is fixed for each generated HTML). 122 | let files = getFiles(path) 123 | var idx = 0 124 | var fileSet = initHashSet[string]() 125 | var duplSet = initHashSet[string]() 126 | for file in files: 127 | let baseName = file.extractFilename() 128 | let relPath = file.removePrefix(path).removeSuffix(baseName) 129 | let prefix = relPath.strip(chars = {'/'}) # remove possible trailing `/` 130 | .split('/') # split path parts 131 | .join(".") # concat by `.` instead 132 | var outfile = baseName.replace(".nim", ".html") 133 | if outfile in fileSet: 134 | duplSet.incl outfile 135 | else: 136 | fileSet.incl outfile 137 | outfile = docPath / outfile 138 | echo "Processing: ", outfile, " [", idx, "/", files.len, "]" 139 | # NOTE: Changing the current working directory to the project path is required in order for 140 | # `git.commit:` to work! Otherwise we sit in `docs` and for some reason the relative path 141 | # will eat one piece of the resulting `source` links and thereby removing the actual branch 142 | # and we end up with a broken link! 143 | echo execAction(&"cd {baseDir} && {nim} doc {defStr} --git.url:{gitUrl} --git.commit:{masterBranch} --git.devel:{masterBranch} -o:{outfile} --index:on {file}") 144 | inc idx 145 | ## now build the index 146 | echo execAction(&"{nim} buildIndex -o:{docPath}/theindex.html {docPath}") 147 | when declared(getNimRootDir): 148 | #[ 149 | NOTE: running it locally doesn't work anymore on modern chromium browser, 150 | because they block "access from origin 'null' due to CORS policy". 151 | this enables doc search, works at least locally with: 152 | cd {docPath} && python -m SimpleHTTPServer 9009 153 | ]# 154 | echo execAction(&"{nim} js -o:{docPath}/dochack.js {getNimRootDir()}/tools/dochack/dochack.nim") 155 | 156 | # echo "Processed files: ", fileSet 157 | if duplSet.card > 0: 158 | echo "WARNING: Duplicate filenames detected: ", duplSet 159 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | * ggplotnim - ggplot2 in Nim 2 | [[https://github.com/Vindaar/ggplotnim/workflows/ggplotnim%20CI/badge.svg]] 3 | [[https://matrix.to/#/#nim-science:envs.net][https://img.shields.io/static/v1?message=join%20chat&color=blue&label=nim-science&logo=matrix&logoColor=gold&style=flat-square&.svg]] 4 | [[https://discord.gg/f5hA9UK3dY][https://img.shields.io/discord/371759389889003530?color=blue&label=nim-science&logo=discord&logoColor=gold&style=flat-square&.svg]] 5 | 6 | This package, as the name suggests, will become a "sort of" port of 7 | [[https://ggplot2.tidyverse.org/][ggplot2]] for Nim. 8 | 9 | It is based on the [[https://github.com/vindaar/ginger/][ginger]] package. 10 | 11 | If you're unfamiliar with the Grammar of Graphics to create plots, one 12 | of the best resources is probably Hadley Wickham's book on =ggplot2=, 13 | for which also an online version exists at: 14 | https://ggplot2-book.org/ 15 | 16 | In general this library tries (and will continue to do so) to stay 17 | mostly compliant with the =ggplot2= syntax. So searching for a 18 | solution in =ggplot2= should hopefully be applicable to this (unless 19 | the feature isn't implemented yet of course). 20 | 21 | ** Note on version =v0.4.0= 22 | 23 | The dataframe implementation that was part of this library until 24 | version =v0.4.0= is now in its own repository under the name of 25 | =Datamancer=: 26 | 27 | [[https://github.com/SciNim/Datamancer]] 28 | 29 | This library imports and exports =Datamancer=, as such you don't need to 30 | change any code, even if you only imported =ggplotnim= to access the dataframe. 31 | 32 | ** Recipes 33 | 34 | For a more nimish approach, check out the [[file:recipes.org][recipes]], which should give 35 | you examples for typical use cases and things I encountered and the 36 | solutions I found. Please feel free to add examples to this file to 37 | help other people! 38 | 39 | Note that all recipes shown there are part of the test suite. So it's 40 | guaranteed that the plots shown there for a given version actually 41 | produce the shown result! 42 | 43 | ** Documentation 44 | 45 | The documentation is found at: 46 | 47 | https://vindaar.github.io/ggplotnim 48 | 49 | ** Installation & dependencies 50 | 51 | Installation should be just a 52 | #+BEGIN_SRC sh 53 | nimble install ggplotnim 54 | #+END_SRC 55 | away. Maybe consider installing the =#head=, since new version 56 | probably won't be released after every change, due to rapid 57 | development still ongoing. 58 | 59 | Since this library is written from scratch there is only a single 60 | external dependency, which is =cairo=. 61 | 62 | *** Windows 63 | 64 | Using =ggplotnim= on Windows is made slightly more problematic, 65 | because of the default =cairo= backend. Installing =cairo= on Windows 66 | is not as straightforward as on Linux or OSX. 67 | 68 | There are multiple options, from most complicated to easiest: 69 | - installing a program, which also uses =cairo= on Windows, for 70 | example =emacs= and adding said program to Windows' PATH. Some 71 | instructions here: 72 | https://gist.github.com/Vindaar/6cb4e93baff3e1ab88a7ab7ed1ae5686 73 | - using @pietroppeter's approach to only install the shared libraries 74 | that are actually required, see here: 75 | https://gist.github.com/pietroppeter/80266c634b22b3861273089dab3e1af2 76 | - or to thank @preshing's work and use his standalone single DLL for 77 | =cairo= on windows: 78 | https://github.com/preshing/cairo-windows/ 79 | See how it's used in the Github Actions workflow for Windows here: 80 | https://github.com/Vindaar/ggplotnim/blob/master/.github/workflows/ci.yml#L61-L64 81 | 82 | Personally I would recommend the last option. Note however that the 83 | standalone DLL is called =cairo.dll=, but =ggplotnim= expects the name 84 | =libcairo-2.dll=. I would recommend to put the DLL in some sane place 85 | and adding that location to your Windows PATH variable: 86 | 87 | Simple text only instructions on how to do that: 88 | #+begin_quote 89 | - =Win= key 90 | - search for "path" 91 | - click on “edit system environment variables” 92 | - click on “Environment Variables” in the bottom right corner 93 | - under “System variables” select “PATH” and click edit 94 | - click “New” and add the full path to your installation location of 95 | choice that contains the now called =libcairo-2.dll= 96 | #+end_quote 97 | 98 | After saving those changes and restarting PowerShell / the command 99 | prompt everything should work. 100 | 101 | ** Currently working features 102 | 103 | Geoms: 104 | - =geom_point= 105 | - =geom_line= 106 | - =geom_histogram= 107 | - =geom_freqpoly= 108 | - =geom_bar= 109 | - =geom_errorbar= 110 | - =geom_linerange= 111 | - =geom_tile= 112 | - =geom_raster= 113 | - =geom_text= 114 | - =geom_ridgeline= 115 | - *soon:* 116 | - =geom_density= 117 | 118 | Facets: 119 | - =facet_wrap= 120 | 121 | Scales: 122 | - size (both for discrete and continuous data) 123 | - color (both for discrete and continuous data) 124 | - shape (multiple shapes for lines and points) 125 | 126 | ** Examples 127 | 128 | *Consider looking at the [[file:recipes.org][recipes]] in addition to the below to get a 129 | fuller picture!* 130 | 131 | The following is a short example from the recipe section that shows 132 | multiple features: 133 | - parsing CSV files to a DF 134 | - performing DF operations using formulas (=f{}= syntax) 135 | - general =ggplot= functionality 136 | - composing multiple geoms to annotate specific datapoints 137 | 138 | #+BEGIN_SRC nim 139 | import ggplotnim 140 | let df = toDf(readCsv("data/mpg.csv")) 141 | let dfMax = df.mutate(f{"mpgMean" ~ (`cty` + `hwy`) / 2.0}) 142 | .arrange("mpgMean") 143 | .tail(1) 144 | ggplot(df, aes("hwy", "displ")) + 145 | geom_point(aes(color = "cty")) + # set point specific color mapping 146 | # Add the annotation for the car model below the point 147 | geom_text(data = dfMax, 148 | aes = aes(y = f{c"displ" - 0.2}, 149 | text = "model")) + 150 | # and add another annotation of the mean mpg above the point 151 | geom_text(data = dfMax, 152 | aes = aes(y = f{c"displ" + 0.2}, 153 | text = "mpgMean")) + 154 | theme_opaque() + 155 | ggsave("media/recipes/rAnnotateMaxValues.png") 156 | #+END_SRC 157 | 158 | 159 | [[./media/recipes/rAnnotateMaxValues.png]] 160 | 161 | ** *Experimental* Vega-Lite backend 162 | 163 | From the beginning one of my goals for this library was to provide not 164 | only a Cairo backend, but also to support [[https://vega.github.io/vega-lite/][Vega-Lite]] (or possibly Vega) 165 | as a backend. 166 | To share plots and data online (and possibly add support for 167 | interactive features) is much easier in such a way. 168 | 169 | An experimental version is implemented in [[https://github.com/Vindaar/ggplotnim/blob/master/src/ggplotnim/ggplot_vega.nim][ggplot_vega.nim]], which 170 | provides most functionality of the native backend, with the exception 171 | of support for facetted plots. 172 | 173 | See the [[https://github.com/Vindaar/ggplotnim/blob/master/recipes.org#simple-vega-lite-example][full example in the recipe here]]. 174 | 175 | Creating a vega plot is done by also importing the =ggplot_vega= 176 | submodule and then just replacing a =ggsave= call by a =ggvega= call: 177 | #+begin_src nim 178 | import ggplotnim 179 | import ggplotnim/ggplot_vega 180 | let mpg = toDf(readCsv("data/mpg.csv")) 181 | ggplot(mpg, aes(x = "displ", y = "cty", color = "class")) + 182 | geom_point() + 183 | ggtitle("ggplotnim in Vega-Lite!") + 184 | ggvega("media/recipes/rSimpleVegaLite.html") # w/o arg creates a `/tmp/vega_lite_plot.html` 185 | #+end_src 186 | 187 | This recipe gives us the following plot: 188 | 189 | [[media/recipes/rSimpleVegaLite.png]] 190 | 191 | To view it as an interactive plot in the Vega viewer, [[https://vega.github.io/editor/?#/gist/0bef3ed0cf7c6d26da927732f1c81582/rSimpleVegaLite.json][click here]]. 192 | 193 | -------------------------------------------------------------------------------- /src/ggplotnim/ggplot_styles.nim: -------------------------------------------------------------------------------- 1 | import ggplot_types, ggplot_scales, ggplot_utils 2 | import colormaps / colormaps 3 | import ginger except Scale 4 | import datamancer 5 | #[ 6 | Contains procs dealing with styles and defines defaults for 7 | different geoms. 8 | ]# 9 | 10 | ## The default styles we use for each geom in case neither the user provides 11 | ## a setting nor a mapping 12 | from chroma import parseHex 13 | const StatSmoothColor = parseHex("3366FF") # color used by ggplot2 for smoothed lines 14 | const PointDefaultStyle = Style(size: 3.0, 15 | marker: mkCircle, 16 | color: black, 17 | fillColor: black) 18 | const LineDefaultStyle* = Style(lineWidth: 1.0, 19 | lineType: ltSolid, 20 | size: 5.0, # used to draw error bar 'T' horizontal 21 | color: grey20, 22 | fillColor: transparent) 23 | const DensityDefaultStyle* = Style(lineWidth: 1.0, 24 | lineType: ltSolid, 25 | size: 5.0, # used to draw error bar 'T' horizontal 26 | color: grey20, 27 | fillColor: transparent) 28 | const SmoothDefaultStyle = Style(lineWidth: 2.0, 29 | lineType: ltSolid, 30 | size: 5.0, # used to draw error bar 'T' horizontal 31 | color: StatSmoothColor, 32 | fillColor: transparent) 33 | const BarDefaultStyle = Style(lineWidth: 1.0, 34 | lineType: ltSolid, 35 | color: grey20, 36 | fillColor: grey20) 37 | const HistoDefaultStyle* = Style(lineWidth: 0.2, 38 | lineType: ltSolid, 39 | color: grey20, 40 | fillColor: grey20) 41 | const TileDefaultStyle = Style(lineWidth: 0.05, 42 | lineType: ltSolid, 43 | color: grey20, 44 | fillColor: grey20) 45 | const TextDefaultStyle = Style(font: font(12.0), 46 | size: 12.0, 47 | color: black) 48 | 49 | const DefaultSizeRange* = (low: 2.0, high: 7.0) 50 | const DefaultAlphaRange* = (low: 0.1, high: 1.0) 51 | ## Not a `const`, as that forces us to make color maps const variables too! 52 | let DefaultColorScale* = viridis() 53 | 54 | func defaultStyle(geomKind: GeomKind, statKind: StatKind): Style = 55 | case geomKind 56 | of gkPoint: 57 | result = PointDefaultStyle 58 | of gkLine, gkFreqPoly, gkErrorBar: 59 | case statKind 60 | of stSmooth: result = SmoothDefaultStyle 61 | of stDensity: result = DensityDefaultStyle 62 | else: result = LineDefaultStyle 63 | of gkBar: 64 | result = BarDefaultStyle 65 | of gkHistogram: 66 | result = HistoDefaultStyle 67 | of gkTile: 68 | result = TileDefaultStyle 69 | of gkText: 70 | result = TextDefaultStyle 71 | of gkRaster: 72 | # raster doesn't have default style atm (what would that imply?) 73 | discard 74 | 75 | proc useOrDefault*(c: ColorScale): ColorScale = 76 | ## Either uses the given `ColorScale` (if it defines any colors) or falls back 77 | ## to our default 78 | ## 79 | ## This exists to make sure that a `FilledGeom` receives a `colorScale` field 80 | ## with certainty. Otherwise we can end up in the situation that the user makes use 81 | ## of a function like `scale_fill_continuous()` (which doesn't set a color scale) 82 | ## and we suddenly don't have one. 83 | ## 84 | ## We could make sure to assign a color scale for every procedure returning a color 85 | ## related `Scale`, but given that we don't have an `Option[T]` field, it's more sane 86 | ## to handle it like this (in case new procs are added for example). 87 | result = if c.colors.len == 0: DefaultColorScale 88 | else: c 89 | 90 | func mergeUserStyle*(s: GgStyle, fg: FilledGeom): Style = 91 | ## merges the given `Style` with the desired `userStyle`. 92 | # Have to differentiate between 3 cases of priority: 93 | # 1. `uStyle` 94 | # 2. `s` 95 | # 3. default style for a given geom 96 | let uStyle = fg.geom.userStyle 97 | let geomKind = fg.geomKind 98 | let statKind = fg.geom.statKind # for "smooth" use different style 99 | 100 | template fillField(field: untyped): untyped = 101 | if uStyle.field.isSome: 102 | result.field = uStyle.field.unsafeGet 103 | elif s.field.isSome: 104 | result.field = s.field.unsafeGet 105 | else: 106 | result.field = defaultStyle(geomKind, statKind).field 107 | fillField(color) 108 | fillField(size) 109 | fillField(lineType) 110 | fillField(lineWidth) 111 | fillField(fillColor) 112 | fillField(marker) 113 | fillField(errorBarKind) 114 | fillField(font) 115 | # now apply `alpha` to `fillColor` 116 | if uStyle.alpha.isSome: 117 | result.fillColor.a = uStyle.alpha.unsafeGet 118 | ## TODO: should we apply this to other geoms as well? This would lead to also 119 | ## making the outlines of fills change alpha. Might be wanted, might not be wanted. 120 | ## Or add a `fillAlpha` option? How does ggplot2 handle this? 121 | if geomKind in {gkPoint, gkLine, gkErrorBar, gkText}: 122 | result.color.a = uStyle.alpha.unsafeGet 123 | elif s.alpha.isSome: 124 | result.fillColor.a = s.alpha.unsafeGet 125 | if geomKind in {gkPoint, gkLine, gkErrorBar, gkText}: 126 | result.color.a = uStyle.alpha.unsafeGet 127 | 128 | # apply `color`, `size` to font 129 | ## TODO: This will overwrite a user style!! 130 | if result.color != result.fillColor: 131 | result.font.color = result.color 132 | let defSize = defaultStyle(geomKind, statKind).size 133 | if result.size != defSize: 134 | result.font.size = result.size * 2.5 135 | 136 | proc changeStyle*(s: GgStyle, scVal: ScaleValue): GgStyle = 137 | ## returns a modified style with the appropriate field replaced 138 | result = s 139 | case scVal.kind 140 | of scColor: 141 | result.color = some(scVal.color) 142 | of scFillColor: 143 | # for FillColor we set both the stroke and fill color to the 144 | # same value 145 | result.color = some(scVal.color) 146 | result.fillColor = some(scVal.color) 147 | of scAlpha: # change alpha of both fill and color 148 | result.alpha = some(scVal.alpha) 149 | of scSize: 150 | result.size = some(scVal.size) 151 | of scShape: 152 | result.marker = some(scVal.marker) 153 | result.lineType = some(scVal.lineType) 154 | else: 155 | raise newException(Exception, "Setting style of " & $scVal.kind & " not " & 156 | "supported at the moment!") 157 | 158 | proc applyStyle*[T: string | FormulaNode](style: var GgStyle, df: DataFrame, scales: seq[Scale], 159 | keys: seq[(T, Value)]) = 160 | var styleVal: ScaleValue 161 | for (col, val) in keys: 162 | for s in scales: 163 | # walk all scales and build the correct style 164 | if s.scKind in {scLinearData, scTransformedData, scText}: continue 165 | case s.dcKind 166 | of dcDiscrete: 167 | when T is string: 168 | let isCol = col in df 169 | else: 170 | let isCol = col.isColumn(df) 171 | if not isCol: 172 | # constant value 173 | styleVal = s.getValue(evaluate(s.col)) 174 | elif $col == $s.col: 175 | # else only get value if this `col` is the scales column! 176 | styleVal = if val.kind == VNull and (%~ $col) in s.valueMap: s.getValue(%~ $col) 177 | elif val in s.valueMap: s.getValue(val) 178 | else: continue 179 | else: continue 180 | style = changeStyle(style, styleVal) 181 | else: 182 | 183 | discard 184 | -------------------------------------------------------------------------------- /docs/vega_utils.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | vega_utils 21 | 22 | 23 | 24 | 25 | 69 | 70 | 71 | 72 |
73 |
74 |

vega_utils

75 |
76 |
77 |
78 | 82 |     Dark Mode 83 |
84 | 88 |
89 | Search: 91 |
92 |
93 | Group by: 94 | 98 |
99 |
    100 |
  • 101 | Imports 102 |
      103 | 104 |
    105 |
  • 106 |
  • 107 | Procs 108 | 113 |
  • 114 | 115 |
116 | 117 |
118 |
119 |
120 | 121 |

122 |
123 |

Imports

124 |
125 | formula, ggplot_types 126 |
127 |
128 |

Procs

129 |
130 | 131 |
proc toVegaLite(p: GgPlot): JsonNode
132 |
133 | 134 | converts a GgPlot object to a vega-lite conform JsonNode 135 | 136 |
137 | 138 |
139 | 140 |
141 |
142 | 143 |
144 | 149 |
150 |
151 |
152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /docs/ggplot_utils.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ggplot_utils 21 | 22 | 23 | 24 | 25 | 69 | 70 | 71 | 72 |
73 |
74 |

ggplot_utils

75 |
76 |
77 |
78 | 82 |     Dark Mode 83 |
84 | 88 |
89 | Search: 91 |
92 |
93 | Group by: 94 | 98 |
99 | 110 | 111 |
112 |
113 |
114 | 115 |

116 |
117 |

Procs

118 |
119 | 120 |
proc calcRowsColumns(rows, columns: int; nPlots: int): (int, int) {...}{.raises: [], tags: [].}
121 |
122 | 123 | Calculates the desired rows and columns for # of nPlots given the user's input for rows and columns.
  • If no input is given, calculate the next possible rectangle of plots that favors columns over rows.
  • 124 |
  • If either row or column is 0, sets this dimension to 1
  • 125 |
  • If either row or column is -1, calculate square of nPlots for rows / cols
  • 126 |
  • If both row and column is -1 or either -1 and the other 0, default back to the next possible square.
  • 127 |
128 | 129 | 130 |
131 | 132 |
133 | 134 |
135 |
136 | 137 |
138 | 143 |
144 |
145 |
146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /tests/testsFormula.nim: -------------------------------------------------------------------------------- 1 | import ggplotnim, unittest, sequtils, math, strutils, streams, sugar 2 | import seqmath 3 | 4 | type 5 | Foo = object 6 | fd: float 7 | 8 | suite "Formulas": 9 | let a = [1, 2, 3] 10 | let b = [3, 4, 5] 11 | let c = [4, 5, 6] 12 | let d = [8, 9, 10] 13 | let e = [11, 12, 13] 14 | let f = [false, true, false] 15 | let g = ["hello", "world", "foo"] 16 | let h = [2.5, 7.5, NaN] 17 | let df = seqsToDf(a, b, c, d, e, f, g, h) 18 | test "Basic `idx` tests with automatic type deduction from context": 19 | block: 20 | # - infix, "a" read as integer automatically 21 | let fn = f{ idx("a") == 5 } 22 | check fn.evaluate(df).bCol == [false, false, false].toTensor 23 | block: 24 | # - infix, a read as float automatically 25 | let fn = f{ idx("a") == 5.5 } 26 | check fn.evaluate(df).bCol == [false, false, false].toTensor 27 | block: 28 | # - infix involving `in`, type conversion on `idx` and set 29 | let fn = f{ idx("a").int8 in {1'i8, 3, 5, 7} } 30 | check fn.evaluate(df).bCol == [true, false, true].toTensor 31 | block: 32 | # - infix of `>` works 33 | # - type determined automatically 34 | let fn = f{ 5 > idx("a") } 35 | check fn.evaluate(df).bCol == [true, true, true].toTensor 36 | block: 37 | # - infix of `>` works w/ order switched around 38 | # - type determined automatically 39 | let fn = f{ idx("a") > 5 } 40 | check fn.evaluate(df).bCol == [false, false, false].toTensor 41 | block: 42 | # - type deduction on one side works with `Value` 43 | let fn = f{ idx("a") >= %~ 5.5 } 44 | check fn.evaluate(df).bCol == [false, false, false].toTensor 45 | block: 46 | # - reads data as `bool` 47 | # - runtime error due to a, b being int 48 | ## TODO: decide if this should become a CT error due to ambiguity. 49 | ## Probably yes, requires change to `assignType` I suppose (not to use 50 | ## default type info here) 51 | expect(ValueError): 52 | let fn = f{ idx("a") > idx("b") } 53 | discard fn.evaluate(df) 54 | block: 55 | # - RHS is float, infix means LHS will be read as float 56 | let fn = f{idx("a") < idx("b").float } 57 | check fn.evaluate(df).bCol == [true, true, true].toTensor 58 | block: 59 | # - above works with `==` too 60 | let fn = f{ idx("a") == idx("b").float } 61 | check fn.evaluate(df).bCol == [false, false, false].toTensor 62 | block: 63 | var fm = Foo(fd: 5.2) 64 | let fn = f{ idx("a") > fm.fd } 65 | check fn.evaluate(df).bCol == [false, false, false].toTensor 66 | 67 | block: 68 | # - prefix, automatic type deduction 69 | let fn = f{ not idx("f") } 70 | check fn.evaluate(df).bCol == [true, false, true].toTensor 71 | block: 72 | let fn = f{ idx("x") >= max(col("x")) * 0.5 } 73 | 74 | block: 75 | let fn = f{ parseInt(idx("a")) > 2 } 76 | 77 | test "Basic `col` test with type deduction from context": 78 | block: 79 | ## the following fails at CT, because type of output is ambiguous (max is overloaded) 80 | # let fn = f{ col("a").max } 81 | ## This one should always work 82 | let fn2 = f{float: col("a").max } 83 | check fn2.reduce(df).toInt == 3 84 | 85 | block: 86 | # - accessing column length works 87 | let fn = f{float: col("a").len } 88 | check fn.reduce(df).toInt == 3 89 | 90 | block: 91 | # - accessing tensor elments with bracket 92 | let fn = f{float: col("a")[1] } 93 | check fn.reduce(df).toInt == 2 94 | 95 | test "Automatic type deduction based on nnkDotExpr w/ a (non ambiguous) proc call": 96 | block: 97 | # - examples of determining type from unique procedure in a case where 98 | # heuristic type extraction fails 99 | proc uniqueProcWithType(x: int): int = 100 | x + 5 101 | let fn = f{ idx("a").uniqueProcWithType } 102 | check fn.evaluate(df).iCol == [6, 7, 8].toTensor 103 | 104 | test "Automatic type deduction based on `idx` in argument of a call overloaded proc call": 105 | block: 106 | # - type deduction based on `idx` in specific argument of a typically overloaded 107 | # symbol. Can be deduced due to only single overload matching the arguments 108 | proc someInt(): int = 2 109 | proc max(x: int, y: string, z: float, b: int): int = 110 | result = 5 111 | let fn = f{ max(idx("a"), "hello", 5.5, someInt()) } 112 | check fn.evaluate(df).iCol == [5, 5, 5].toTensor 113 | 114 | block: 115 | # - automatically determines that `a` should be read as `int` 116 | # - formula is mapping 117 | let fn = f{ max(idx("a"), 2) } 118 | check fn.evaluate(df).iCol == [2, 2, 3].toTensor 119 | 120 | test "Formula with an if expression accessing multiple columns": 121 | block: 122 | # - formula with an if expression accessing multiple columns 123 | let fn = f{int -> int: if `a` < 2: 124 | `b` 125 | else: 126 | `c` } 127 | check fn.evaluate(df).iCol == [3, 5, 6].toTensor 128 | 129 | when (NimMajor, NimMinor, NimPatch) >= (1, 4, 0): 130 | block: 131 | ## TODO: 1. we need the parenthesis (otherwise lexer error) 132 | ## 2. return type is deduced to be bool. It should be taken from 133 | ## the if expression! `nnkIfExpr` not implemented yet. 134 | let fn = f{float -> float: "h" ~ (if classify(idx("h")) == fcNaN: 135 | -1.0 136 | else: 137 | `h`)} 138 | check fn.evaluate(df).fCol == [2.5, 7.5, -1.0].toTensor 139 | 140 | test "Dot expression requiring `Value` input works automatically": 141 | block: 142 | # - dot call requiring `Value` argument, output is object column (because 143 | # `isNull` returns a boolean as a `Value` 144 | let fn = f{ idx("a").isNull } 145 | check fn.evaluate(df).oCol == [%~ false, %~ false, %~ false].toTensor 146 | 147 | test "Infix with `notin` and local array": 148 | block: 149 | # - `notin` works and determines `g` 150 | let existKeys = ["hello"] 151 | let fn = f{string: `g` notin existKeys} 152 | check fn.evaluate(df).bCol == [false, true, true].toTensor 153 | 154 | test "`ggplotnim` formula accessing (proc) field of an object": 155 | block: 156 | type 157 | MS = object 158 | trans: proc(x: float): float 159 | let col = %~ "a" 160 | let ms = MS(trans: (proc(x: float): float = 5.5)) 161 | let colStr = "log10(x4)" 162 | let fn = f{float: colStr ~ ms.trans( df[col.toStr][idx] ) } 163 | check fn.evaluate(df).fCol == [5.5, 5.5, 5.5].toTensor 164 | 165 | test "`max` overload is resolved in context of infix with float": 166 | block: 167 | let fn = f{ `a` >= max(`a`) * 0.5 } 168 | check fn.evaluate(df).bCol == [false, true, true].toTensor 169 | 170 | block: 171 | ## TODO: this is technically broken, because from `*` we take `float` 172 | ## as result and from the integer `-1` we determine the infix to be 173 | ## integer 174 | #let fn = f{ -1 * c"hwy"} 175 | 176 | test "Reducing formula with boolean return value": 177 | block: 178 | let df2 = seqsToDf({"var1" : toSeq(0 ..< 10)}) 179 | let fn = f{ sum(`var1`) > 20000 } 180 | check fn.reduce(df2).toBool == false 181 | 182 | test "Example of no `idx` but reducing proc (mean) as a mapping": 183 | block: 184 | ## example of a formula that contradicts our assumption that we should error in 185 | ## case the determined formula kind and the given one mismatch. 186 | ## In this case we might *want* to assign something + the mean for each element in 187 | ## the DF (in the context of a `group_by` call this makes sense! 188 | ## We'll turn it into a warning. 189 | ## Also: keep in mind that if the user writes something, same as with type hints, we 190 | ## should value that decision. 191 | # here we only check it compiles (no CT error anymore) 192 | let fn = f{float -> float: "subMeanHwy" ~ 0.0 + mean(col("hwy"))} 193 | 194 | test "Name test": 195 | let f = f{"meanCty" ~ (c"hwy" + c"cty")} 196 | # name is the full name. Manual parens (nnkPar) are included in representation. 197 | check f.name == "(~ meanCty ((+ hwy cty)))" 198 | 199 | test "Constant mapping of integer": 200 | let countCol = "count" 201 | let fn = f{int: countCol ~ 0} 202 | check fn.evaluate(df).iCol == [0, 0, 0].toTensor 203 | 204 | test "Name of long formula": 205 | const cut_rms_trans_low = 0.1 206 | const cut_rms_trans_high = 1.5 207 | proc inRegion(x, y: float, r: string): bool = 208 | discard 209 | 210 | let fn = f{float -> bool: 211 | `rmsTransverse` >= cut_rms_trans_low and 212 | `rmsTransverse` <= cut_rms_trans_high and 213 | inRegion(df["centerX"][idx], df["centerY"][idx], "crSilver") and 214 | `hits` < 500} 215 | 216 | check $fn == """(and (and (and (>= rmsTransverse cut_rms_trans_low) (<= rmsTransverse cut_rms_trans_high)) (inRegion df["centerX"][idx] df["centerY"][idx] crSilver)) (< hits 500))""" 217 | -------------------------------------------------------------------------------- /src/ggplotnim/ggplot_scales.nim: -------------------------------------------------------------------------------- 1 | import tables, sets, macros, strutils 2 | import datamancer 3 | 4 | import ggplot_types, ggplot_utils 5 | import ginger except Scale 6 | 7 | #[ 8 | Contains procs dealing with `ggplot.Scale`. 9 | ]# 10 | 11 | proc scaleFromData*(c: Column, s: Scale, ignoreInf: static bool = true): ginger.Scale = 12 | ## Combination of `colMin`, `colMax` to avoid running over the data 13 | ## twice. For large DFs to plot this makes a big difference. 14 | ## 15 | ## The input `Scale` is just for error messages. 16 | if c.len == 0: return (low: 0.0, high: 0.0) 17 | case c.kind 18 | of colFloat, colInt, colObject: 19 | # if we have a `colObject` here it (barring no bugs) means it was determined 20 | # to be continuous. That means it can be converted to float, because it is numeric 21 | # as long as we drop null values, which we do. 22 | let t = c.toTensor(float, dropNulls = true) 23 | var 24 | minVal = t[0] 25 | maxVal = t[0] 26 | for x in t: 27 | when ignoreInf: 28 | mixin fcNegInf, fcInf, classify # `import std/math` would work too 29 | if (classify(x) == fcNegInf or 30 | classify(x) == fcInf): 31 | continue 32 | minVal = min(x, minVal) 33 | maxVal = max(x, maxVal) 34 | result = (low: minVal, high: maxVal) 35 | of colConstant: 36 | # for a constant it can be valid, as long as the value is int / float 37 | let cVal = c.cCol 38 | if cVal.kind in {VInt, VFloat}: 39 | result = (low: cVal.toFloat, high: cVal.toFloat) 40 | else: 41 | raise newException(ValueError, "The input column `" & $s.col & "` is constant " & 42 | " with value: " & $cVal & ". Cannot compute a numeric scale from it.") 43 | of colBool, colString, colNone: 44 | raise newException(ValueError, "The input column `" & $s.col & "` is of kind " & $c.kind & 45 | " and thus discrete. `scaleFromData` should never be called.") 46 | of colGeneric: 47 | raise newException(ValueError, "The input column `" & $s.col & "` is of kind " & $c.kind & 48 | ". Generic columns are not supported yet.") 49 | 50 | proc getColName*(s: Scale): string = 51 | ## returns the name of the referred column of the given Scale `s`. 52 | ## Usually this is just the stringification of `s.col`, but for 53 | ## `scTransformedData`, we have to assign to a column, which includes 54 | ## the name of the transformation, so that we don't override the 55 | ## existing column. This is because otherwise we modify the DF (we call 56 | ## `mutateInplace`) and will apply the transformations multiple times 57 | ## if several geoms are plotted! 58 | if s.isNil: return "" # can happen even in sane code paths! 59 | case s.scKind 60 | of scTransformedData: 61 | ## TODO: determine name of `trans` based on transformation proc! 62 | ## We create a new column for transformed data, because this allows us 63 | ## to avoid deep copying the input data frame. 64 | result = "log10(" & evaluate(s.col).toStr & ")" 65 | else: 66 | result = evaluate(s.col).toStr 67 | 68 | proc getValue*(s: Scale, label: Value): ScaleValue = 69 | ## returns the `ScaleValue` of the given Scale `s` for `label` 70 | result = s.valueMap[label] 71 | 72 | proc getLabelKey*(s: Scale, at: int): Value = 73 | ## returns the key at index `at` for the Scale `s` 74 | result = s.labelSeq[at] 75 | 76 | iterator enumerateLabels*(s: Scale): Value = 77 | for k in s.labelSeq: 78 | yield k 79 | 80 | iterator enumerateLabelPairs*(s: Scale): (int, Value) = 81 | for i, k in s.labelSeq: 82 | yield (i, k) 83 | 84 | iterator pairs*(s: Scale): (Value, ScaleValue) = 85 | for k, v in s.valueMap: 86 | yield (k, v) 87 | 88 | iterator values*(s: Scale): ScaleValue = 89 | for v in values(s.valueMap): 90 | yield v 91 | 92 | iterator keys*(s: Scale): Value = 93 | ## NOTE: for clarity use `enumerateLabels` instead! 94 | for k in keys(s.valueMap): 95 | yield k 96 | 97 | iterator enumerateScalesByIds*(filledScales: FilledScales): Scale = 98 | ## yields all scales from the FilledScales that are required for the 99 | ## default backends, which use ginger, i.e. not vega. All missing fields 100 | ## are accessed in a different manner. 101 | template genYield(field: untyped): untyped = 102 | if filledScales.field.main.isSome: 103 | yield filledScales.field.main.get 104 | for m in filledScales.field.more: 105 | yield m 106 | # color Scale 107 | genYield(x) 108 | genYield(y) 109 | genYield(color) 110 | genYield(fill) 111 | genYield(size) 112 | genYield(shape) 113 | genYield(yRidges) 114 | 115 | iterator enumerateScalesByIdsVega*(filledScales: FilledScales): Scale = 116 | ## yields ``all`` scales from the FilledScales for Vega. 117 | template genYield(field: untyped): untyped = 118 | if filledScales.field.main.isSome: 119 | yield filledScales.field.main.get 120 | for m in filledScales.field.more: 121 | yield m 122 | # color Scale 123 | genYield(x) 124 | genYield(y) 125 | genYield(color) 126 | genYield(fill) 127 | genYield(size) 128 | genYield(shape) 129 | genYield(xMin) 130 | genYield(xMax) 131 | genYield(yMin) 132 | genYield(yMax) 133 | genYield(width) 134 | genYield(height) 135 | genYield(text) 136 | genYield(weight) 137 | genYield(yRidges) 138 | 139 | 140 | iterator enumerateScales*(filledScales: FilledScales, geom: Geom): Scale = 141 | ## Yields all scales, which are allowed for the given geom 142 | var yieldedSet = initHashSet[Scale]() 143 | for s in enumerateScalesByIds(filledScales): 144 | if geom.gid in s.ids and s notin yieldedSet: 145 | yieldedSet.incl s 146 | yield s 147 | 148 | iterator enumerateScalesVega*(filledScales: FilledScales, geom: Geom): Scale = 149 | ## Yields all scales, which are allowed for the given geom 150 | var yieldedSet = initHashSet[Scale]() 151 | for s in enumerateScalesByIdsVega(filledScales): 152 | if geom.gid in s.ids and s notin yieldedSet: 153 | yieldedSet.incl s 154 | yield s 155 | 156 | macro genGetScale(field: untyped): untyped = 157 | let name = ident("get" & $field.strVal.capitalizeAscii & "Scale") 158 | result = quote do: 159 | proc `name`*(filledScales: FilledScales, geom = Geom(gid: 0)): Scale = 160 | result = new Scale 161 | if filledScales.`field`.main.isSome: 162 | # use main 163 | result = filledScales.`field`.main.get 164 | else: 165 | # find scale matching `gid` 166 | for s in filledScales.`field`.more: 167 | if geom.gid == 0 or geom.gid in s.ids: 168 | return s 169 | 170 | macro genGetOptScale(field: untyped): untyped = 171 | let name = ident("get" & $field.strVal.capitalizeAscii & "Scale") 172 | result = quote do: 173 | proc `name`*(filledScales: FilledScales, geom = Geom(gid: 0)): Option[Scale] = 174 | #result = new Scale 175 | if filledScales.`field`.main.isSome: 176 | # use main 177 | result = some(filledScales.`field`.main.get) 178 | else: 179 | # find scale matching `gid` 180 | for s in filledScales.`field`.more: 181 | if geom.gid == 0 or geom.gid in s.ids: 182 | return some(s) 183 | 184 | 185 | genGetScale(x) 186 | genGetScale(y) 187 | genGetOptScale(fill) 188 | genGetOptScale(xMin) 189 | genGetOptScale(yMin) 190 | genGetOptScale(xMax) 191 | genGetOptScale(yMax) 192 | genGetOptScale(width) 193 | genGetOptScale(height) 194 | genGetScale(text) 195 | genGetScale(yRidges) 196 | genGetOptScale(weight) 197 | # not used at the moment 198 | #genGetScale(color) 199 | #genGetScale(size) 200 | #genGetScale(shape) 201 | 202 | func updateAesRidges*(p: GgPlot): GgPlot = 203 | ## Adds the `ridges` information to the `GgPlot` object by assigning 204 | ## the `yRidges` aesthetic to the global aesthetic and forcing its 205 | ## scale to be discrete. 206 | doAssert p.ridges.isSome 207 | let ridge = p.ridges.unsafeGet 208 | let scale = some(Scale(scKind: scLinearData, col: ridge.col, axKind: akY, 209 | hasDiscreteness: true, # force scale to be discrete! 210 | dcKind: dcDiscrete, 211 | ids: {0'u16 .. high(uint16)})) 212 | result = p 213 | result.aes.yRidges = scale 214 | 215 | func getSecondaryAxis*(filledScales: FilledScales, axKind: AxisKind): SecondaryAxis = 216 | ## Assumes a secondary axis must exist! 217 | case axKind 218 | of akX: 219 | let xScale = filledScales.getXScale() 220 | result = xScale.secondaryAxis.unwrap() 221 | of akY: 222 | let yScale = filledScales.getYScale() 223 | result = yScale.secondaryAxis.unwrap() 224 | 225 | func hasSecondary*(filledScales: FilledScales, axKind: AxisKind): bool = 226 | case axKind 227 | of akX: 228 | let xScale = filledScales.getXScale() 229 | if xScale.secondaryAxis.isSome: 230 | result = true 231 | of akY: 232 | let yScale = filledScales.getYScale() 233 | if yScale.secondaryAxis.isSome: 234 | result = true 235 | 236 | func isEmpty*(s: ginger.Scale): bool = 237 | ## checks if the given scale is empty 238 | result = s.low == s.high 239 | 240 | func mergeScales*(s1, s2: ginger.Scale): ginger.Scale = 241 | ## merges the two data scales and returns a version encompassing both 242 | ## TODO: think about how we might allow (0.0, 0.0) for cases where the 243 | ## input data has N elements all 0. This is useful for e.g. 244 | ## having 0 data, but assigning only a yMin = -1. That results in a 245 | ## useable (-1, 0) data scale! Currently would not work! 246 | if not (s1.isEmpty and s1.low == 0): 247 | result = (low: min(s1.low, s2.low), 248 | high: max(s1.high, s2.high)) 249 | else: 250 | result = s2 251 | 252 | func encompassingDataScale*(scales: seq[Scale], 253 | axKind: AxisKind, 254 | baseScale: ginger.Scale = (low: 0.0, high: 0.0)): ginger.Scale = 255 | ## calculate the encompassing data scale spanned by all 256 | ## given `scales` of kind `scLinearData`, `scTransformedData`. 257 | if not baseScale.isEmpty: 258 | result = baseScale 259 | for s in scales: 260 | if s.scKind in {scLinearData, scTransformedData} and 261 | s.axKind == axKind: 262 | result = mergeScales(result, s.dataScale) 263 | 264 | proc findData*(fg: FilledGeom, label: Value): DataFrame = 265 | ## returns the `DataFrame` of the given `fg` of that label, which 266 | ## contains `label` in `yieldData` 267 | result = newDataFrame() 268 | for key, val in fg.yieldData: 269 | if key.kind == VObject and label.kind == VObject and label in key: 270 | # multiple keys may match, add DFs! 271 | result.add val[2] 272 | 273 | proc findData*(fs: FilledScales, label: Value): DataFrame = 274 | ## returns the `DataFrame` of matching `label` of all `FilledGeoms`. 275 | ## contains `label` in `yieldData` 276 | var data = newSeq[DataFrame]() 277 | for fg in fs.geoms: 278 | data.add findData(fg, label) 279 | result = assignStack(data) 280 | if result.len == 0: # use the input data frame instead. No facet plot used 281 | result = fs.inputData 282 | --------------------------------------------------------------------------------